viveworker 0.2.1 → 0.4.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 +55 -0
- package/package.json +16 -2
- package/scripts/a2a-cli.mjs +189 -0
- package/scripts/a2a-executor.mjs +126 -0
- package/scripts/a2a-handler.mjs +439 -0
- package/scripts/a2a-relay-client.mjs +394 -0
- package/scripts/com.viveworker.moltbook-scout.plist.sample +58 -0
- package/scripts/moltbook-api.mjs +206 -0
- package/scripts/moltbook-cli.mjs +1372 -0
- package/scripts/moltbook-scout-auto.sh +447 -0
- package/scripts/moltbook-scout-run.sh +14 -0
- package/scripts/moltbook-watcher.mjs +294 -0
- package/scripts/viveworker-bridge.mjs +814 -16
- package/scripts/viveworker-claude-hook.mjs +7 -1
- package/scripts/viveworker.mjs +314 -1
- package/web/app.css +104 -3
- package/web/app.js +445 -28
- package/web/i18n.js +90 -0
- package/web/sw.js +1 -1
package/web/app.js
CHANGED
|
@@ -47,6 +47,7 @@ const state = {
|
|
|
47
47
|
pairError: "",
|
|
48
48
|
pairNotice: "",
|
|
49
49
|
pushStatus: null,
|
|
50
|
+
moltbookScoutStatus: null,
|
|
50
51
|
pushNotice: "",
|
|
51
52
|
pushError: "",
|
|
52
53
|
deviceNotice: "",
|
|
@@ -212,6 +213,7 @@ async function refreshAuthenticatedState() {
|
|
|
212
213
|
await refreshTimeline();
|
|
213
214
|
await refreshDevices();
|
|
214
215
|
await refreshPushStatus();
|
|
216
|
+
await fetchMoltbookScoutStatus();
|
|
215
217
|
ensureCurrentSelection();
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -317,6 +319,18 @@ async function refreshPushStatus() {
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
322
|
+
async function fetchMoltbookScoutStatus() {
|
|
323
|
+
if (!state.session?.moltbookEnabled) {
|
|
324
|
+
state.moltbookScoutStatus = null;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
state.moltbookScoutStatus = await apiGet("/api/moltbook/scout-status");
|
|
329
|
+
} catch {
|
|
330
|
+
state.moltbookScoutStatus = null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
320
334
|
async function getClientPushState() {
|
|
321
335
|
const registration = state.serviceWorkerRegistration || (await navigator.serviceWorker?.ready.catch(() => null));
|
|
322
336
|
if (registration) {
|
|
@@ -526,11 +540,18 @@ function listInboxEntries() {
|
|
|
526
540
|
|
|
527
541
|
function normalizeProviderClient(value) {
|
|
528
542
|
const normalized = String(value || "").toLowerCase();
|
|
529
|
-
|
|
543
|
+
if (normalized === "claude") return "claude";
|
|
544
|
+
if (normalized === "moltbook") return "moltbook";
|
|
545
|
+
if (normalized === "a2a") return "a2a";
|
|
546
|
+
return "codex";
|
|
530
547
|
}
|
|
531
548
|
|
|
532
549
|
function providerDisplayName(provider) {
|
|
533
|
-
|
|
550
|
+
const p = normalizeProviderClient(provider);
|
|
551
|
+
if (p === "claude") return L("common.claude");
|
|
552
|
+
if (p === "moltbook") return "Moltbook";
|
|
553
|
+
if (p === "a2a") return "A2A";
|
|
554
|
+
return L("common.codex");
|
|
534
555
|
}
|
|
535
556
|
|
|
536
557
|
function entryMatchesProviderFilter(item) {
|
|
@@ -633,6 +654,16 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
|
|
|
633
654
|
}
|
|
634
655
|
}
|
|
635
656
|
|
|
657
|
+
function isMoltbookThreadId(threadId, item) {
|
|
658
|
+
if (threadId === "moltbook") return true;
|
|
659
|
+
if (typeof threadId === "string" && threadId.startsWith("draft:")) return true;
|
|
660
|
+
const kind = normalizeClientText(item?.kind || "");
|
|
661
|
+
if (kind === "moltbook_draft" || kind === "moltbook_reply") return true;
|
|
662
|
+
const label = normalizeClientText(item?.threadLabel || "").toLowerCase();
|
|
663
|
+
if (label === "moltbook") return true;
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
|
|
636
667
|
function completedThreads() {
|
|
637
668
|
const items = Array.isArray(state.inbox?.completed) ? state.inbox.completed : [];
|
|
638
669
|
if (!items.length) {
|
|
@@ -641,7 +672,7 @@ function completedThreads() {
|
|
|
641
672
|
const byThread = new Map();
|
|
642
673
|
for (const item of items) {
|
|
643
674
|
const threadId = normalizeClientText(item.threadId || "");
|
|
644
|
-
if (!threadId) {
|
|
675
|
+
if (!threadId || isMoltbookThreadId(threadId, item)) {
|
|
645
676
|
continue;
|
|
646
677
|
}
|
|
647
678
|
const latestAtMs = Number(item.createdAtMs || 0);
|
|
@@ -666,7 +697,7 @@ function diffThreads() {
|
|
|
666
697
|
const byThread = new Map();
|
|
667
698
|
for (const item of items) {
|
|
668
699
|
const threadId = normalizeClientText(item.threadId || "");
|
|
669
|
-
if (!threadId) {
|
|
700
|
+
if (!threadId || isMoltbookThreadId(threadId, item)) {
|
|
670
701
|
continue;
|
|
671
702
|
}
|
|
672
703
|
const latestAtMs = Number(item.createdAtMs || 0);
|
|
@@ -933,6 +964,27 @@ function shouldDeferRenderForActiveInteraction() {
|
|
|
933
964
|
) {
|
|
934
965
|
return true;
|
|
935
966
|
}
|
|
967
|
+
if (
|
|
968
|
+
activeElement instanceof HTMLTextAreaElement &&
|
|
969
|
+
activeElement.matches("[data-moltbook-draft-textarea]")
|
|
970
|
+
) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
if (state.currentDetail?.kind === "moltbook_draft") {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
if (
|
|
977
|
+
activeElement instanceof HTMLTextAreaElement &&
|
|
978
|
+
activeElement.matches("[data-claude-question-note]")
|
|
979
|
+
) {
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
if (
|
|
983
|
+
activeElement instanceof HTMLInputElement &&
|
|
984
|
+
activeElement.matches("[data-moltbook-draft-title]")
|
|
985
|
+
) {
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
936
988
|
if (
|
|
937
989
|
activeElement instanceof HTMLSelectElement &&
|
|
938
990
|
activeElement.matches("[data-timeline-thread-select], [data-diff-thread-select], [data-completed-thread-select]")
|
|
@@ -1650,6 +1702,12 @@ function providerBadgeMeta(provider) {
|
|
|
1650
1702
|
if (normalized === "claude") {
|
|
1651
1703
|
return { id: "claude", label: L("common.claude"), glyph: "C" };
|
|
1652
1704
|
}
|
|
1705
|
+
if (normalized === "moltbook") {
|
|
1706
|
+
return { id: "moltbook", label: "Moltbook", glyph: "M" };
|
|
1707
|
+
}
|
|
1708
|
+
if (normalized === "a2a") {
|
|
1709
|
+
return { id: "a2a", label: "A2A", glyph: "A" };
|
|
1710
|
+
}
|
|
1653
1711
|
return { id: "codex", label: L("common.codex"), glyph: "X" };
|
|
1654
1712
|
}
|
|
1655
1713
|
|
|
@@ -1659,11 +1717,18 @@ function renderProviderBadge(provider) {
|
|
|
1659
1717
|
}
|
|
1660
1718
|
|
|
1661
1719
|
function providerFilterOptions() {
|
|
1662
|
-
|
|
1720
|
+
const options = [
|
|
1663
1721
|
{ id: "all", label: L("timeline.allThreads") },
|
|
1664
1722
|
{ id: "codex", label: L("common.codex") },
|
|
1665
1723
|
{ id: "claude", label: L("common.claude") },
|
|
1666
1724
|
];
|
|
1725
|
+
if (state.session?.moltbookEnabled === true) {
|
|
1726
|
+
options.push({ id: "moltbook", label: "Moltbook" });
|
|
1727
|
+
}
|
|
1728
|
+
if (state.session?.a2aEnabled === true) {
|
|
1729
|
+
options.push({ id: "a2a", label: "A2A" });
|
|
1730
|
+
}
|
|
1731
|
+
return options;
|
|
1667
1732
|
}
|
|
1668
1733
|
|
|
1669
1734
|
function renderProviderFilter() {
|
|
@@ -2679,6 +2744,7 @@ function buildSettingsContext() {
|
|
|
2679
2744
|
supportsPushValue,
|
|
2680
2745
|
serverEnabled,
|
|
2681
2746
|
}),
|
|
2747
|
+
moltbookScout: state.moltbookScoutStatus,
|
|
2682
2748
|
};
|
|
2683
2749
|
}
|
|
2684
2750
|
|
|
@@ -2825,6 +2891,13 @@ function settingsPageMeta(page) {
|
|
|
2825
2891
|
description: L("settings.awayMode.copy"),
|
|
2826
2892
|
icon: "settings",
|
|
2827
2893
|
};
|
|
2894
|
+
case "moltbook":
|
|
2895
|
+
return {
|
|
2896
|
+
id: "moltbook",
|
|
2897
|
+
title: L("settings.moltbook.title"),
|
|
2898
|
+
description: L("settings.moltbook.copy"),
|
|
2899
|
+
icon: "item",
|
|
2900
|
+
};
|
|
2828
2901
|
default:
|
|
2829
2902
|
return settingsPageMeta("notifications");
|
|
2830
2903
|
}
|
|
@@ -2894,6 +2967,15 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
2894
2967
|
`
|
|
2895
2968
|
}
|
|
2896
2969
|
${renderSettingsGroup(L("settings.group.general"), generalRows)}
|
|
2970
|
+
${state.session?.moltbookEnabled ? renderSettingsGroup(L("common.sns"), [
|
|
2971
|
+
renderSettingsNavRow({
|
|
2972
|
+
page: "moltbook",
|
|
2973
|
+
icon: "item",
|
|
2974
|
+
title: L("settings.moltbook.title"),
|
|
2975
|
+
subtitle: L("settings.moltbook.subtitle"),
|
|
2976
|
+
value: context.moltbookScout?.enabled ? `${context.moltbookScout.sentToday} / ${context.moltbookScout.maxDaily}` : "",
|
|
2977
|
+
}),
|
|
2978
|
+
]) : ""}
|
|
2897
2979
|
${renderSettingsGroup(L("settings.pairing.title"), deviceRows)}
|
|
2898
2980
|
${renderSettingsGroup(L("settings.group.advanced"), advancedRows)}
|
|
2899
2981
|
</div>
|
|
@@ -2937,6 +3019,9 @@ function renderSettingsSubpage(context, { mobile }) {
|
|
|
2937
3019
|
case "awayMode":
|
|
2938
3020
|
content = renderSettingsAwayModePage();
|
|
2939
3021
|
break;
|
|
3022
|
+
case "moltbook":
|
|
3023
|
+
content = renderSettingsMoltbookPage(context);
|
|
3024
|
+
break;
|
|
2940
3025
|
default:
|
|
2941
3026
|
content = "";
|
|
2942
3027
|
}
|
|
@@ -2954,8 +3039,8 @@ function renderSettingsNotificationsPage(context) {
|
|
|
2954
3039
|
const { push, permission, secureContext, standalone, supportsPushValue, serverEnabled } = context;
|
|
2955
3040
|
const statusRows = [
|
|
2956
3041
|
renderSettingsInfoRow(L("settings.row.status"), L(context.setupState.notifications.labelKey)),
|
|
2957
|
-
renderSettingsInfoRow(L("settings.row.notificationPermission"), permission),
|
|
2958
|
-
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), push.serverSubscribed ? L("common.
|
|
3042
|
+
renderSettingsInfoRow(L("settings.row.notificationPermission"), L(`settings.permission.${permission}`) || permission),
|
|
3043
|
+
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), push.serverSubscribed ? L("common.enabled") : L("common.disabled")),
|
|
2959
3044
|
push.lastSuccessfulDeliveryAtMs
|
|
2960
3045
|
? renderSettingsInfoRow(
|
|
2961
3046
|
L("settings.row.lastSuccessfulDelivery"),
|
|
@@ -2967,10 +3052,10 @@ function renderSettingsNotificationsPage(context) {
|
|
|
2967
3052
|
<div class="settings-page">
|
|
2968
3053
|
${renderSettingsGroup("", statusRows)}
|
|
2969
3054
|
${renderSettingsGroup(L("settings.group.advanced"), [
|
|
2970
|
-
renderSettingsInfoRow(L("settings.row.serverWebPush"), serverEnabled ? L("common.
|
|
2971
|
-
renderSettingsInfoRow(L("settings.row.secureContext"), secureContext ? L("common.
|
|
2972
|
-
renderSettingsInfoRow(L("settings.row.homeScreenApp"), standalone ? L("common.
|
|
2973
|
-
renderSettingsInfoRow(L("settings.row.browserSupport"), supportsPushValue ? L("common.
|
|
3055
|
+
renderSettingsInfoRow(L("settings.row.serverWebPush"), serverEnabled ? L("common.enabled") : L("common.disabled")),
|
|
3056
|
+
renderSettingsInfoRow(L("settings.row.secureContext"), secureContext ? L("common.supported") : L("common.notSupported")),
|
|
3057
|
+
renderSettingsInfoRow(L("settings.row.homeScreenApp"), standalone ? L("common.supported") : L("common.notSupported")),
|
|
3058
|
+
renderSettingsInfoRow(L("settings.row.browserSupport"), supportsPushValue ? L("common.supported") : L("common.notSupported")),
|
|
2974
3059
|
])}
|
|
2975
3060
|
${state.pushNotice ? `<p class="inline-alert inline-alert--success">${escapeHtml(state.pushNotice)}</p>` : ""}
|
|
2976
3061
|
${state.pushError ? `<p class="inline-alert inline-alert--danger">${escapeHtml(state.pushError)}</p>` : ""}
|
|
@@ -3062,12 +3147,12 @@ function renderSettingsAdvancedPage(context) {
|
|
|
3062
3147
|
<div class="settings-page">
|
|
3063
3148
|
${context.diagnostics.map((message) => `<p class="inline-alert">${escapeHtml(message)}</p>`).join("")}
|
|
3064
3149
|
${renderSettingsGroup("", [
|
|
3065
|
-
renderSettingsInfoRow(L("settings.row.serverWebPush"), context.serverEnabled ? L("common.
|
|
3066
|
-
renderSettingsInfoRow(L("settings.row.secureContext"), context.secureContext ? L("common.
|
|
3067
|
-
renderSettingsInfoRow(L("settings.row.homeScreenApp"), context.standalone ? L("common.
|
|
3068
|
-
renderSettingsInfoRow(L("settings.row.notificationPermission"), context.permission),
|
|
3069
|
-
renderSettingsInfoRow(L("settings.row.browserSupport"), context.supportsPushValue ? L("common.
|
|
3070
|
-
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), context.push.serverSubscribed ? L("common.
|
|
3150
|
+
renderSettingsInfoRow(L("settings.row.serverWebPush"), context.serverEnabled ? L("common.enabled") : L("common.disabled")),
|
|
3151
|
+
renderSettingsInfoRow(L("settings.row.secureContext"), context.secureContext ? L("common.supported") : L("common.notSupported")),
|
|
3152
|
+
renderSettingsInfoRow(L("settings.row.homeScreenApp"), context.standalone ? L("common.supported") : L("common.notSupported")),
|
|
3153
|
+
renderSettingsInfoRow(L("settings.row.notificationPermission"), L(`settings.permission.${context.permission}`) || context.permission),
|
|
3154
|
+
renderSettingsInfoRow(L("settings.row.browserSupport"), context.supportsPushValue ? L("common.supported") : L("common.notSupported")),
|
|
3155
|
+
renderSettingsInfoRow(L("settings.row.currentDeviceSubscribed"), context.push.serverSubscribed ? L("common.enabled") : L("common.disabled")),
|
|
3071
3156
|
context.push.lastSuccessfulDeliveryAtMs
|
|
3072
3157
|
? renderSettingsInfoRow(
|
|
3073
3158
|
L("settings.row.lastSuccessfulDelivery"),
|
|
@@ -3152,13 +3237,50 @@ function renderSettingsAwayModePage() {
|
|
|
3152
3237
|
`;
|
|
3153
3238
|
}
|
|
3154
3239
|
|
|
3240
|
+
function renderSettingsMoltbookPage(context) {
|
|
3241
|
+
const scout = context.moltbookScout;
|
|
3242
|
+
if (!scout?.enabled) {
|
|
3243
|
+
return `
|
|
3244
|
+
<div class="settings-page">
|
|
3245
|
+
<p class="settings-page-copy muted">${escapeHtml(L("settings.moltbook.unavailable"))}</p>
|
|
3246
|
+
</div>
|
|
3247
|
+
`;
|
|
3248
|
+
}
|
|
3249
|
+
const batchRows = scout.batch ? [
|
|
3250
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchCandidates"), String(scout.batch.candidateCount)),
|
|
3251
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchTopScore"), String(scout.batch.topScore)),
|
|
3252
|
+
renderSettingsInfoRow(L("settings.row.moltbookBatchRemaining"), `${Math.floor(scout.batch.remainingSeconds / 60)}:${String(scout.batch.remainingSeconds % 60).padStart(2, "0")}`),
|
|
3253
|
+
] : [];
|
|
3254
|
+
return `
|
|
3255
|
+
<div class="settings-page">
|
|
3256
|
+
${renderSettingsGroup("", [
|
|
3257
|
+
renderSettingsInfoRow(L("settings.row.moltbookQuota"), `${scout.sentToday} / ${scout.maxDaily}`),
|
|
3258
|
+
renderSettingsInfoRow(L("settings.row.moltbookComposed"), `${scout.composedToday || 0} / 3`),
|
|
3259
|
+
renderSettingsInfoRow(L("settings.row.moltbookSeenPosts"), String(scout.seenPostCount)),
|
|
3260
|
+
])}
|
|
3261
|
+
${batchRows.length ? renderSettingsGroup(L("settings.moltbook.batchTitle"), batchRows) : ""}
|
|
3262
|
+
${Array.isArray(scout.recentComposeTitles) && scout.recentComposeTitles.length
|
|
3263
|
+
? renderSettingsGroup(L("settings.row.moltbookRecentTitles"), scout.recentComposeTitles.map((t) => {
|
|
3264
|
+
const title = typeof t === "string" ? t : (t.title || "");
|
|
3265
|
+
const postId = typeof t === "object" ? t.postId : "";
|
|
3266
|
+
const display = postId
|
|
3267
|
+
? `<a href="https://www.moltbook.com/post/${escapeHtml(postId)}" target="_blank" rel="noopener">${escapeHtml(title)}</a>`
|
|
3268
|
+
: escapeHtml(title);
|
|
3269
|
+
return renderSettingsInfoRow("", display, { rawValue: true, rowClassName: "settings-info-row--stacked" });
|
|
3270
|
+
}))
|
|
3271
|
+
: ""}
|
|
3272
|
+
</div>
|
|
3273
|
+
`;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3155
3276
|
function renderSettingsInfoRow(label, value, options = {}) {
|
|
3156
3277
|
const rowClassName = ["settings-info-row", options.rowClassName || ""].filter(Boolean).join(" ");
|
|
3157
3278
|
const valueClassName = ["settings-info-row__value", options.valueClassName || ""].filter(Boolean).join(" ");
|
|
3279
|
+
const displayValue = options.rawValue ? value : escapeHtml(value);
|
|
3158
3280
|
return `
|
|
3159
3281
|
<div class="${rowClassName}">
|
|
3160
3282
|
<span class="settings-info-row__label">${escapeHtml(label)}</span>
|
|
3161
|
-
<span class="${valueClassName}">${
|
|
3283
|
+
<span class="${valueClassName}">${displayValue}</span>
|
|
3162
3284
|
</div>
|
|
3163
3285
|
`;
|
|
3164
3286
|
}
|
|
@@ -3193,7 +3315,7 @@ function renderDeviceSection(title, devices, emptyMessage) {
|
|
|
3193
3315
|
|
|
3194
3316
|
function renderTrustedDeviceCard(device) {
|
|
3195
3317
|
const localeLabel = localeDisplayName(device.locale, state.locale) || device.locale || L("common.unavailable");
|
|
3196
|
-
const pushLabel = device.pushSubscribed ? L("common.
|
|
3318
|
+
const pushLabel = device.pushSubscribed ? L("common.enabled") : L("common.disabled");
|
|
3197
3319
|
const badge = device.currentDevice
|
|
3198
3320
|
? `<span class="device-card__badge">${escapeHtml(L("settings.device.thisDevice"))}</span>`
|
|
3199
3321
|
: "";
|
|
@@ -3264,13 +3386,18 @@ function renderStandardDetailDesktop(detail) {
|
|
|
3264
3386
|
<div class="detail-shell">
|
|
3265
3387
|
${renderDetailMetaRow(detail, kindInfo)}
|
|
3266
3388
|
<h2 class="detail-title detail-title--desktop">${escapeHtml(detailDisplayTitle(detail))}</h2>
|
|
3267
|
-
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3389
|
+
${detail.readOnly || detail.kind === "approval" || detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" ? "" : renderDetailLead(detail, kindInfo)}
|
|
3268
3390
|
${renderPreviousContextCard(detail)}
|
|
3269
3391
|
${renderInterruptedDetailNotice(detail)}
|
|
3392
|
+
${renderMoltbookReplyComposer(detail)}
|
|
3393
|
+
${renderMoltbookDraftComposer(detail)}
|
|
3394
|
+
${renderA2ATaskDetail(detail)}
|
|
3270
3395
|
${
|
|
3271
|
-
|
|
3272
|
-
?
|
|
3273
|
-
:
|
|
3396
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task"
|
|
3397
|
+
? ""
|
|
3398
|
+
: plainIntro
|
|
3399
|
+
? plainIntro
|
|
3400
|
+
: `
|
|
3274
3401
|
<section class="detail-card detail-card--body ${spaciousBodyDetail ? "detail-card--message-body" : ""}">
|
|
3275
3402
|
<div class="detail-body ${spaciousBodyDetail ? "detail-body--message " : ""}markdown">${detail.messageHtml || ""}</div>
|
|
3276
3403
|
</section>
|
|
@@ -3299,10 +3426,15 @@ function renderStandardDetailMobile(detail) {
|
|
|
3299
3426
|
${renderDetailMetaRow(detail, kindInfo, { mobile: true })}
|
|
3300
3427
|
${renderPreviousContextCard(detail, { mobile: true })}
|
|
3301
3428
|
${renderInterruptedDetailNotice(detail, { mobile: true })}
|
|
3429
|
+
${renderMoltbookReplyComposer(detail, { mobile: true })}
|
|
3430
|
+
${renderMoltbookDraftComposer(detail, { mobile: true })}
|
|
3431
|
+
${renderA2ATaskDetail(detail, { mobile: true })}
|
|
3302
3432
|
${
|
|
3303
|
-
|
|
3304
|
-
?
|
|
3305
|
-
:
|
|
3433
|
+
detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task"
|
|
3434
|
+
? ""
|
|
3435
|
+
: plainIntro
|
|
3436
|
+
? plainIntro
|
|
3437
|
+
: `
|
|
3306
3438
|
<section class="detail-card detail-card--body detail-card--mobile ${spaciousBodyDetail ? "detail-card--message-body" : ""}">
|
|
3307
3439
|
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo, { mobile: true })}
|
|
3308
3440
|
<div class="detail-body ${spaciousBodyDetail ? "detail-body--message " : ""}markdown">${detail.messageHtml || ""}</div>
|
|
@@ -3327,7 +3459,8 @@ function renderDetailPlainIntro(detail, options = {}) {
|
|
|
3327
3459
|
if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
|
|
3328
3460
|
return "";
|
|
3329
3461
|
}
|
|
3330
|
-
|
|
3462
|
+
const ak = normalizeClientText(detail?.approvalKind || "");
|
|
3463
|
+
if (detail?.kind === "approval" && ak !== "file" && ak !== "plan" && ak !== "question") {
|
|
3331
3464
|
return "";
|
|
3332
3465
|
}
|
|
3333
3466
|
if (!detail?.messageHtml) {
|
|
@@ -3732,7 +3865,7 @@ function renderClaudeQuestionSection(detail, options = {}) {
|
|
|
3732
3865
|
${noticeHtml}
|
|
3733
3866
|
${isReadOnly ? "" : `
|
|
3734
3867
|
<div class="claude-question-actions">
|
|
3735
|
-
<button type="submit" class="
|
|
3868
|
+
<button type="submit" class="primary primary--wide" ${isSent || draft.sending ? "disabled" : ""}>
|
|
3736
3869
|
${escapeHtml(draft.sending ? L("claudeQuestion.send", { provider }) + "…" : sendLabel)}
|
|
3737
3870
|
</button>
|
|
3738
3871
|
</div>
|
|
@@ -3755,6 +3888,150 @@ function setClaudeQuestionDraft(token, partial) {
|
|
|
3755
3888
|
return next;
|
|
3756
3889
|
}
|
|
3757
3890
|
|
|
3891
|
+
function renderMoltbookReplyComposer(detail, options = {}) {
|
|
3892
|
+
if (detail.kind !== "moltbook_reply") return "";
|
|
3893
|
+
const rawTitle = normalizeClientText(detail.title || "");
|
|
3894
|
+
const match = rawTitle.match(/^@([^\s]+)/u);
|
|
3895
|
+
const authorHandle = match ? match[1] : detail.commentAuthor || "";
|
|
3896
|
+
const postUrl = detail.postUrl || "";
|
|
3897
|
+
const postLink = postUrl
|
|
3898
|
+
? `<p class="reply-composer__author"><a href="${escapeHtml(postUrl)}" target="_blank" rel="noopener">${escapeHtml(postUrl)}</a></p>`
|
|
3899
|
+
: "";
|
|
3900
|
+
const bodyHtml = detail.messageHtml
|
|
3901
|
+
? `<div class="reply-composer__context-body markdown">${detail.messageHtml}</div>`
|
|
3902
|
+
: "";
|
|
3903
|
+
return `
|
|
3904
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3905
|
+
<div class="reply-composer reply-composer--readonly">
|
|
3906
|
+
<div class="reply-composer__copy">
|
|
3907
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">Moltbook</span>
|
|
3908
|
+
${authorHandle ? `<p class="reply-composer__author">from <strong>@${escapeHtml(authorHandle)}</strong></p>` : ""}
|
|
3909
|
+
${postLink}
|
|
3910
|
+
</div>
|
|
3911
|
+
${bodyHtml}
|
|
3912
|
+
</div>
|
|
3913
|
+
</section>
|
|
3914
|
+
`;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
function renderMoltbookDraftComposer(detail, options = {}) {
|
|
3918
|
+
if (detail.kind !== "moltbook_draft") return "";
|
|
3919
|
+
const enabled = detail.moltbookDraftEnabled !== false && detail.readOnly !== true;
|
|
3920
|
+
const draftText = detail.draftText || "";
|
|
3921
|
+
const isOriginalPost = detail.draftType === "original_post";
|
|
3922
|
+
const postAuthorLine = !isOriginalPost && detail.postAuthor
|
|
3923
|
+
? `<p class="reply-composer__author-meta muted">@${escapeHtml(detail.postAuthor)}</p>`
|
|
3924
|
+
: "";
|
|
3925
|
+
const postLink = !isOriginalPost && detail.postUrl
|
|
3926
|
+
? `<p class="reply-composer__author"><a href="${escapeHtml(detail.postUrl)}" target="_blank" rel="noopener">${escapeHtml(detail.postTitle || detail.postUrl)}</a></p>`
|
|
3927
|
+
: "";
|
|
3928
|
+
const postBodyBlock = !isOriginalPost && detail.postBody
|
|
3929
|
+
? `<details class="reply-composer__context"><summary>元の投稿</summary><div class="reply-composer__context-body">${escapeHtml(detail.postBody).replace(/\n/g, "<br>")}</div></details>`
|
|
3930
|
+
: "";
|
|
3931
|
+
const intentBlock = detail.intent
|
|
3932
|
+
? `<div class="reply-composer__intent"><span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(L("moltbook.draft.intent"))}</span><p>${escapeHtml(detail.intent).replace(/\n/g, "<br>")}</p></div>`
|
|
3933
|
+
: "";
|
|
3934
|
+
const titleInput = isOriginalPost && enabled
|
|
3935
|
+
? `<label class="field reply-field reply-field--title"><span class="field-label">${escapeHtml(L("moltbook.draft.titleLabel"))}</span><input type="text" name="title" class="reply-field__input" value="${escapeHtml(detail.postTitle || "")}" data-moltbook-draft-title /></label>`
|
|
3936
|
+
: isOriginalPost
|
|
3937
|
+
? `<p class="reply-composer__title-display"><strong>${escapeHtml(detail.postTitle || "")}</strong></p>`
|
|
3938
|
+
: "";
|
|
3939
|
+
const submoltBadge = isOriginalPost && detail.submoltName
|
|
3940
|
+
? `<span class="eyebrow-pill eyebrow-pill--subtle">${escapeHtml(detail.submoltName)}</span>`
|
|
3941
|
+
: "";
|
|
3942
|
+
const eyebrowLabel = isOriginalPost ? L("moltbook.draft.eyebrowPost") : L("moltbook.draft.eyebrowReply");
|
|
3943
|
+
const approveLabel = isOriginalPost ? L("moltbook.draft.approvePost") : L("moltbook.draft.approveReply");
|
|
3944
|
+
const greetingBlock = (() => {
|
|
3945
|
+
if (!isOriginalPost) return "";
|
|
3946
|
+
const slot = detail.slot || "";
|
|
3947
|
+
const icons = { morning: "\u2600\ufe0f", noon: "\u26c5", evening: "\ud83c\udf19" };
|
|
3948
|
+
const keys = { morning: "moltbook.draft.greetMorning", noon: "moltbook.draft.greetNoon", evening: "moltbook.draft.greetEvening" };
|
|
3949
|
+
const icon = icons[slot] || "\u270d\ufe0f";
|
|
3950
|
+
const msg = keys[slot] ? L(keys[slot]) : L("moltbook.draft.greetFallback");
|
|
3951
|
+
return `<div class="compose-greeting"><span class="compose-greeting__icon">${icon}</span><span class="compose-greeting__text">${escapeHtml(msg)}</span></div>`;
|
|
3952
|
+
})();
|
|
3953
|
+
const buttons = enabled
|
|
3954
|
+
? `
|
|
3955
|
+
<div class="actions actions--stack${options.mobile ? " actions--sticky" : ""}">
|
|
3956
|
+
<button type="submit" data-action="approve" class="primary primary--wide">${escapeHtml(approveLabel)}</button>
|
|
3957
|
+
<button type="submit" data-action="deny" class="danger danger--wide">Deny</button>
|
|
3958
|
+
</div>
|
|
3959
|
+
`
|
|
3960
|
+
: `<p class="muted reply-composer__description">${escapeHtml(L("moltbook.draft.resolved"))}</p>`;
|
|
3961
|
+
const buttonsWrapped = enabled && options.mobile ? `<div class="detail-action-bar">${buttons}</div>` : buttons;
|
|
3962
|
+
return `
|
|
3963
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3964
|
+
<form class="reply-composer" data-moltbook-draft-form data-token="${escapeHtml(detail.token || "")}" ${isOriginalPost ? 'data-draft-type="original_post"' : ""}>
|
|
3965
|
+
<div class="reply-composer__copy">
|
|
3966
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(eyebrowLabel)}</span>
|
|
3967
|
+
${submoltBadge}
|
|
3968
|
+
${greetingBlock}
|
|
3969
|
+
${postLink}
|
|
3970
|
+
${postAuthorLine}
|
|
3971
|
+
${titleInput}
|
|
3972
|
+
${postBodyBlock}
|
|
3973
|
+
${intentBlock}
|
|
3974
|
+
<p class="muted reply-composer__description">${escapeHtml(L("moltbook.draft.editHint"))}</p>
|
|
3975
|
+
</div>
|
|
3976
|
+
<textarea name="text" class="reply-composer__textarea" data-moltbook-draft-textarea rows="8" ${enabled ? "" : "readonly"}>${escapeHtml(draftText)}</textarea>
|
|
3977
|
+
${buttonsWrapped}
|
|
3978
|
+
</form>
|
|
3979
|
+
</section>
|
|
3980
|
+
`;
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
function renderA2ATaskDetail(detail, options = {}) {
|
|
3984
|
+
if (detail.kind !== "a2a_task") return "";
|
|
3985
|
+
const enabled = detail.a2aTaskEnabled !== false && detail.readOnly !== true;
|
|
3986
|
+
const instruction = detail.instruction || "";
|
|
3987
|
+
const callerIp = detail.callerInfo?.ip || "";
|
|
3988
|
+
const callerAgent = detail.callerInfo?.userAgent || "";
|
|
3989
|
+
const callerLine = callerIp
|
|
3990
|
+
? `<p class="reply-composer__author-meta muted">${escapeHtml(L("a2a.task.from"))}: ${escapeHtml(callerIp)}${callerAgent ? ` (${escapeHtml(callerAgent.slice(0, 60))})` : ""}</p>`
|
|
3991
|
+
: "";
|
|
3992
|
+
|
|
3993
|
+
const statusKey = {
|
|
3994
|
+
submitted: "a2a.task.statusSubmitted",
|
|
3995
|
+
working: "a2a.task.statusWorking",
|
|
3996
|
+
completed: "a2a.task.statusCompleted",
|
|
3997
|
+
failed: "a2a.task.statusFailed",
|
|
3998
|
+
canceled: "a2a.task.statusCanceled",
|
|
3999
|
+
rejected: "a2a.task.statusRejected",
|
|
4000
|
+
}[detail.taskStatus] || "a2a.task.statusSubmitted";
|
|
4001
|
+
|
|
4002
|
+
const statusBadge = !enabled
|
|
4003
|
+
? `<span class="eyebrow-pill eyebrow-pill--subtle">${escapeHtml(L(statusKey))}</span>`
|
|
4004
|
+
: "";
|
|
4005
|
+
|
|
4006
|
+
const buttons = enabled
|
|
4007
|
+
? `
|
|
4008
|
+
<div class="actions actions--stack${options.mobile ? " actions--sticky" : ""}">
|
|
4009
|
+
<button type="submit" data-action="approve" class="primary primary--wide">${escapeHtml(L("a2a.task.approve"))}</button>
|
|
4010
|
+
<button type="submit" data-action="deny" class="danger danger--wide">${escapeHtml(L("a2a.task.deny"))}</button>
|
|
4011
|
+
</div>
|
|
4012
|
+
`
|
|
4013
|
+
: `<p class="muted reply-composer__description">${escapeHtml(L("a2a.task.resolved"))}</p>`;
|
|
4014
|
+
const buttonsWrapped = enabled && options.mobile ? `<div class="detail-action-bar">${buttons}</div>` : buttons;
|
|
4015
|
+
|
|
4016
|
+
return `
|
|
4017
|
+
<section class="detail-card detail-card--reply ${options.mobile ? "detail-card--mobile" : ""}">
|
|
4018
|
+
<form class="reply-composer" data-a2a-task-form data-token="${escapeHtml(detail.token || "")}">
|
|
4019
|
+
<div class="reply-composer__copy">
|
|
4020
|
+
<span class="eyebrow-pill eyebrow-pill--quiet">${escapeHtml(L("a2a.task.eyebrow"))}</span>
|
|
4021
|
+
${statusBadge}
|
|
4022
|
+
${callerLine}
|
|
4023
|
+
<p class="muted reply-composer__description">${enabled ? escapeHtml(L("a2a.task.editHint")) : ""}</p>
|
|
4024
|
+
</div>
|
|
4025
|
+
<div class="reply-composer__instruction">
|
|
4026
|
+
<label class="field-label">${escapeHtml(L("a2a.task.instruction"))}</label>
|
|
4027
|
+
<textarea name="instruction" class="reply-composer__textarea" rows="6" ${enabled ? "" : "readonly"}>${escapeHtml(instruction)}</textarea>
|
|
4028
|
+
</div>
|
|
4029
|
+
${buttonsWrapped}
|
|
4030
|
+
</form>
|
|
4031
|
+
</section>
|
|
4032
|
+
`;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
3758
4035
|
function renderCompletionReplyComposer(detail, options = {}) {
|
|
3759
4036
|
if (detail.kind !== "completion" || detail.reply?.enabled !== true) {
|
|
3760
4037
|
return "";
|
|
@@ -4811,6 +5088,143 @@ function bindShellInteractions() {
|
|
|
4811
5088
|
});
|
|
4812
5089
|
}
|
|
4813
5090
|
|
|
5091
|
+
const moltbookDraftForm = document.querySelector("[data-moltbook-draft-form]");
|
|
5092
|
+
if (moltbookDraftForm) {
|
|
5093
|
+
const token = moltbookDraftForm.dataset.token || "";
|
|
5094
|
+
let submittedAction = "approve";
|
|
5095
|
+
moltbookDraftForm.querySelectorAll("button[type='submit']").forEach((btn) => {
|
|
5096
|
+
btn.addEventListener("click", () => {
|
|
5097
|
+
submittedAction = btn.dataset.action || "approve";
|
|
5098
|
+
});
|
|
5099
|
+
});
|
|
5100
|
+
moltbookDraftForm.addEventListener("submit", async (event) => {
|
|
5101
|
+
event.preventDefault();
|
|
5102
|
+
if (moltbookDraftForm.dataset.submitting === "1") return;
|
|
5103
|
+
moltbookDraftForm.dataset.submitting = "1";
|
|
5104
|
+
const buttons = moltbookDraftForm.querySelectorAll("button[type='submit']");
|
|
5105
|
+
const textarea = moltbookDraftForm.querySelector("textarea");
|
|
5106
|
+
const labelCache = new Map();
|
|
5107
|
+
buttons.forEach((btn) => {
|
|
5108
|
+
labelCache.set(btn, btn.innerHTML);
|
|
5109
|
+
btn.disabled = true;
|
|
5110
|
+
if (btn.dataset.action === submittedAction) {
|
|
5111
|
+
btn.innerHTML = submittedAction === "approve" ? "送信中…" : "処理中…";
|
|
5112
|
+
btn.classList.add("is-loading");
|
|
5113
|
+
} else {
|
|
5114
|
+
btn.classList.add("is-dimmed");
|
|
5115
|
+
}
|
|
5116
|
+
});
|
|
5117
|
+
if (textarea) textarea.readOnly = true;
|
|
5118
|
+
const editedText = normalizeClientText(new FormData(moltbookDraftForm).get("text"));
|
|
5119
|
+
const titleInput = moltbookDraftForm.querySelector("[data-moltbook-draft-title]");
|
|
5120
|
+
const editedTitle = titleInput ? normalizeClientText(titleInput.value) : "";
|
|
5121
|
+
try {
|
|
5122
|
+
const decisionBody = { action: submittedAction, editedText };
|
|
5123
|
+
if (editedTitle) decisionBody.editedTitle = editedTitle;
|
|
5124
|
+
const res = await fetch(`/api/items/moltbook-draft/${encodeURIComponent(token)}/decision`, {
|
|
5125
|
+
method: "POST",
|
|
5126
|
+
headers: { "content-type": "application/json" },
|
|
5127
|
+
credentials: "same-origin",
|
|
5128
|
+
body: JSON.stringify(decisionBody),
|
|
5129
|
+
});
|
|
5130
|
+
if (!res.ok) {
|
|
5131
|
+
const errBody = await res.json().catch(() => ({}));
|
|
5132
|
+
alert(`Moltbook draft ${submittedAction} failed: ${errBody.error || res.status}`);
|
|
5133
|
+
buttons.forEach((btn) => {
|
|
5134
|
+
btn.disabled = false;
|
|
5135
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5136
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5137
|
+
});
|
|
5138
|
+
if (textarea) textarea.readOnly = false;
|
|
5139
|
+
moltbookDraftForm.dataset.submitting = "";
|
|
5140
|
+
return;
|
|
5141
|
+
}
|
|
5142
|
+
// Mark local detail as resolved so re-render shows "already resolved" immediately.
|
|
5143
|
+
if (state.currentDetail?.kind === "moltbook_draft") {
|
|
5144
|
+
state.currentDetail.moltbookDraftEnabled = false;
|
|
5145
|
+
state.currentDetail.readOnly = true;
|
|
5146
|
+
}
|
|
5147
|
+
await refreshAuthenticatedState();
|
|
5148
|
+
await renderShell();
|
|
5149
|
+
} catch (error) {
|
|
5150
|
+
alert(`Moltbook draft error: ${error.message}`);
|
|
5151
|
+
buttons.forEach((btn) => {
|
|
5152
|
+
btn.disabled = false;
|
|
5153
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5154
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5155
|
+
});
|
|
5156
|
+
if (textarea) textarea.readOnly = false;
|
|
5157
|
+
moltbookDraftForm.dataset.submitting = "";
|
|
5158
|
+
}
|
|
5159
|
+
});
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5162
|
+
const a2aTaskForm = document.querySelector("[data-a2a-task-form]");
|
|
5163
|
+
if (a2aTaskForm) {
|
|
5164
|
+
const token = a2aTaskForm.dataset.token || "";
|
|
5165
|
+
let submittedAction = "approve";
|
|
5166
|
+
a2aTaskForm.querySelectorAll("button[type='submit']").forEach((btn) => {
|
|
5167
|
+
btn.addEventListener("click", () => {
|
|
5168
|
+
submittedAction = btn.dataset.action || "approve";
|
|
5169
|
+
});
|
|
5170
|
+
});
|
|
5171
|
+
a2aTaskForm.addEventListener("submit", async (event) => {
|
|
5172
|
+
event.preventDefault();
|
|
5173
|
+
if (a2aTaskForm.dataset.submitting === "1") return;
|
|
5174
|
+
a2aTaskForm.dataset.submitting = "1";
|
|
5175
|
+
const buttons = a2aTaskForm.querySelectorAll("button[type='submit']");
|
|
5176
|
+
const textarea = a2aTaskForm.querySelector("textarea");
|
|
5177
|
+
const labelCache = new Map();
|
|
5178
|
+
buttons.forEach((btn) => {
|
|
5179
|
+
labelCache.set(btn, btn.innerHTML);
|
|
5180
|
+
btn.disabled = true;
|
|
5181
|
+
if (btn.dataset.action === submittedAction) {
|
|
5182
|
+
btn.innerHTML = submittedAction === "approve" ? "Executing…" : "Denying…";
|
|
5183
|
+
btn.classList.add("is-loading");
|
|
5184
|
+
} else {
|
|
5185
|
+
btn.classList.add("is-dimmed");
|
|
5186
|
+
}
|
|
5187
|
+
});
|
|
5188
|
+
if (textarea) textarea.readOnly = true;
|
|
5189
|
+
const instruction = normalizeClientText(new FormData(a2aTaskForm).get("instruction"));
|
|
5190
|
+
try {
|
|
5191
|
+
const res = await fetch(`/api/items/a2a-task/${encodeURIComponent(token)}/decision`, {
|
|
5192
|
+
method: "POST",
|
|
5193
|
+
headers: { "content-type": "application/json" },
|
|
5194
|
+
credentials: "same-origin",
|
|
5195
|
+
body: JSON.stringify({ action: submittedAction, instruction }),
|
|
5196
|
+
});
|
|
5197
|
+
if (!res.ok) {
|
|
5198
|
+
const errBody = await res.json().catch(() => ({}));
|
|
5199
|
+
alert(`A2A task ${submittedAction} failed: ${errBody.error || res.status}`);
|
|
5200
|
+
buttons.forEach((btn) => {
|
|
5201
|
+
btn.disabled = false;
|
|
5202
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5203
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5204
|
+
});
|
|
5205
|
+
if (textarea) textarea.readOnly = false;
|
|
5206
|
+
a2aTaskForm.dataset.submitting = "";
|
|
5207
|
+
return;
|
|
5208
|
+
}
|
|
5209
|
+
if (state.currentDetail?.kind === "a2a_task") {
|
|
5210
|
+
state.currentDetail.a2aTaskEnabled = false;
|
|
5211
|
+
state.currentDetail.readOnly = true;
|
|
5212
|
+
}
|
|
5213
|
+
await refreshAuthenticatedState();
|
|
5214
|
+
await renderShell();
|
|
5215
|
+
} catch (error) {
|
|
5216
|
+
alert(`A2A task error: ${error.message}`);
|
|
5217
|
+
buttons.forEach((btn) => {
|
|
5218
|
+
btn.disabled = false;
|
|
5219
|
+
btn.classList.remove("is-loading", "is-dimmed");
|
|
5220
|
+
btn.innerHTML = labelCache.get(btn);
|
|
5221
|
+
});
|
|
5222
|
+
if (textarea) textarea.readOnly = false;
|
|
5223
|
+
a2aTaskForm.dataset.submitting = "";
|
|
5224
|
+
}
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5227
|
+
|
|
4814
5228
|
const replyForm = document.querySelector("[data-completion-reply-form]");
|
|
4815
5229
|
if (replyForm) {
|
|
4816
5230
|
const token = replyForm.dataset.token || "";
|
|
@@ -5289,6 +5703,9 @@ function kindMeta(kind) {
|
|
|
5289
5703
|
return { label: L("common.diff"), tone: "neutral", icon: "diff" };
|
|
5290
5704
|
case "file_event":
|
|
5291
5705
|
return { label: L("common.fileEvent"), tone: "neutral", icon: "file-event" };
|
|
5706
|
+
case "moltbook_reply":
|
|
5707
|
+
case "moltbook_draft":
|
|
5708
|
+
return { label: L("common.sns"), tone: "neutral", icon: "item" };
|
|
5292
5709
|
default:
|
|
5293
5710
|
return { label: L("common.item"), tone: "neutral", icon: "item" };
|
|
5294
5711
|
}
|