viveworker 0.5.1 → 0.5.3
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 +2 -2
- package/package.json +1 -1
- package/scripts/a2a-cli.mjs +1 -1
- package/scripts/moltbook-api.mjs +56 -16
- package/scripts/moltbook-watcher.mjs +30 -11
- package/scripts/viveworker-bridge.mjs +279 -52
- package/web/app.css +75 -1
- package/web/app.js +225 -13
- package/web/i18n.js +56 -2
package/README.md
CHANGED
|
@@ -219,7 +219,7 @@ Open `Settings > Moltbook` in the phone app to see the current auto-scout postin
|
|
|
219
219
|
|
|
220
220
|
- **Receive tasks from other agents** worldwide via standard A2A JSON-RPC
|
|
221
221
|
- **Human-in-the-loop**: every incoming task requires your approval on your phone before execution
|
|
222
|
-
- **Public Agent Card**: your profile at `https://a2a.viveworker.com/<user-id>` tells other agents what you can do
|
|
222
|
+
- **Public Agent Card**: your profile at `https://a2a.viveworker.com/u/<user-id>` tells other agents what you can do
|
|
223
223
|
- **Customizable profile**: description, skills, and avatar are all configurable
|
|
224
224
|
|
|
225
225
|
### How it works
|
|
@@ -250,7 +250,7 @@ The bridge detects the new credentials within 30 seconds and auto-connects.
|
|
|
250
250
|
|
|
251
251
|
### Profile page
|
|
252
252
|
|
|
253
|
-
Visit `https://a2a.viveworker.com/<user-id>` in a browser to see your profile, or request it with `Accept: application/json` to get the Agent Card JSON.
|
|
253
|
+
Visit `https://a2a.viveworker.com/u/<user-id>` in a browser to see your profile, or request it with `Accept: application/json` to get the Agent Card JSON.
|
|
254
254
|
|
|
255
255
|
## Security Model
|
|
256
256
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Local mobile companion for Codex Desktop and Claude Desktop — approvals, code review, Moltbook drafts, and A2A (Agent-to-Agent) task relay on your LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/scripts/a2a-cli.mjs
CHANGED
|
@@ -153,7 +153,7 @@ async function handleSetup(args) {
|
|
|
153
153
|
|
|
154
154
|
console.log(`✅ Credentials saved\n`);
|
|
155
155
|
console.log(`🚀 Setup complete! Restart your viveworker bridge to connect.`);
|
|
156
|
-
console.log(` Your A2A endpoint: ${result.relayUrl}/${result.userId}\n`);
|
|
156
|
+
console.log(` Your A2A endpoint: ${result.relayUrl}/u/${result.userId}\n`);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// ---------------------------------------------------------------------------
|
package/scripts/moltbook-api.mjs
CHANGED
|
@@ -39,22 +39,56 @@ export function createMoltbookClient(apiKey) {
|
|
|
39
39
|
throw new Error("MOLTBOOK_API_KEY is required");
|
|
40
40
|
}
|
|
41
41
|
return async function mb(pathname, init = {}) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
const { timeoutMs: overrideTimeoutMs, ...fetchInit } = init;
|
|
43
|
+
const method = String(fetchInit.method || "GET").toUpperCase();
|
|
44
|
+
const isWrite = method !== "GET" && method !== "HEAD";
|
|
45
|
+
// Reads get 30s, writes 60s. A flaky upstream must not be able to hang
|
|
46
|
+
// the process indefinitely — this exact failure mode took out the
|
|
47
|
+
// moltbook-watcher in mid-April 2026 when /notifications started
|
|
48
|
+
// returning 500s and later stopped responding entirely. Bare fetch has
|
|
49
|
+
// no default timeout and the undici connection pool saturated, leaving
|
|
50
|
+
// the process alive but unable to log, poll, or push anything.
|
|
51
|
+
// Writes get a longer budget because POST /posts and /comments do
|
|
52
|
+
// synchronous spam-screening on the server side.
|
|
53
|
+
const timeoutMs = typeof overrideTimeoutMs === "number"
|
|
54
|
+
? overrideTimeoutMs
|
|
55
|
+
: (isWrite ? 60_000 : 30_000);
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
54
58
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
const res = await fetch(`${API_BASE}${pathname}`, {
|
|
60
|
+
...fetchInit,
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
headers: {
|
|
63
|
+
"content-type": "application/json",
|
|
64
|
+
authorization: `Bearer ${apiKey}`,
|
|
65
|
+
...(fetchInit.headers || {}),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const text = await res.text().catch(() => "");
|
|
69
|
+
if (controller.signal.aborted) {
|
|
70
|
+
const err = new Error("aborted");
|
|
71
|
+
err.name = "AbortError";
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
throw new Error(`moltbook ${res.status} ${pathname}: ${text}`);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(text);
|
|
79
|
+
} catch {
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error?.name === "AbortError") {
|
|
84
|
+
const hint = isWrite
|
|
85
|
+
? " — may have succeeded server-side; run `moltbook reconcile` before retrying"
|
|
86
|
+
: "";
|
|
87
|
+
throw new Error(`moltbook timeout after ${timeoutMs}ms ${method} ${pathname}${hint}`);
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
} finally {
|
|
91
|
+
clearTimeout(timer);
|
|
58
92
|
}
|
|
59
93
|
};
|
|
60
94
|
}
|
|
@@ -173,7 +207,13 @@ export function rollScoutDayIfNeeded(state) {
|
|
|
173
207
|
}
|
|
174
208
|
|
|
175
209
|
export function recordComposeAttempt(state, title, postId, type = "post") {
|
|
176
|
-
|
|
210
|
+
// Only original posts count against the daily "本日の新規投稿数" quota
|
|
211
|
+
// (composedToday). Replies are still appended to recentComposeTitles so
|
|
212
|
+
// the "最近の投稿" list in settings shows them with their reply badge,
|
|
213
|
+
// but they intentionally do not inflate the counter.
|
|
214
|
+
if (type === "post") {
|
|
215
|
+
state.composedToday = (state.composedToday || 0) + 1;
|
|
216
|
+
}
|
|
177
217
|
state.lastComposeDay = todayKey();
|
|
178
218
|
if (!Array.isArray(state.recentComposeTitles)) state.recentComposeTitles = [];
|
|
179
219
|
const entry = { title: String(title || ""), type };
|
|
@@ -71,18 +71,37 @@ try {
|
|
|
71
71
|
const pending = new Map(); // sourceId -> { commentId, postId }
|
|
72
72
|
|
|
73
73
|
async function pushToBridge(item) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
// Loopback HTTPS call to the viveworker bridge. Normally completes in
|
|
75
|
+
// milliseconds, but if the bridge process is hung this fetch — like any
|
|
76
|
+
// unbounded fetch — would wait forever and eventually zombify the watcher
|
|
77
|
+
// (same failure mode that took out the Moltbook poll path in April 2026).
|
|
78
|
+
// 10s is a generous cap for a local call; anything slower means the bridge
|
|
79
|
+
// is already unhealthy and we're better off erroring out and retrying next
|
|
80
|
+
// poll cycle than blocking the whole watcher on it.
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(`${VIVEWORKER_BASE}/api/providers/moltbook/events`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"content-type": "application/json",
|
|
88
|
+
"x-viveworker-hook-secret": HOOK_SECRET,
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify(item),
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
throw new Error(`bridge ${res.status}: ${await res.text().catch(() => "")}`);
|
|
95
|
+
}
|
|
96
|
+
return await res.json();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error?.name === "AbortError") {
|
|
99
|
+
throw new Error(`bridge timeout after 10000ms POST /api/providers/moltbook/events`);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
} finally {
|
|
103
|
+
clearTimeout(timer);
|
|
84
104
|
}
|
|
85
|
-
return res.json();
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
function draftReply() {
|
|
@@ -18,7 +18,7 @@ import { generatePairingCredentials, shouldRotatePairing, upsertEnvText } from "
|
|
|
18
18
|
import { renderMarkdownHtml } from "./lib/markdown-render.mjs";
|
|
19
19
|
import { buildAgentCard, handleA2ARequest, resolveA2ATaskDecision, completeA2ATask, failA2ATask } from "./a2a-handler.mjs";
|
|
20
20
|
import { registerWithRelay, startRelayPolling, stopRelayPolling, postRelayResult, getRelayStatus, updatePublicTasksFlag } from "./a2a-relay-client.mjs";
|
|
21
|
-
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM } from "./moltbook-api.mjs";
|
|
21
|
+
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM, listInboxItems } from "./moltbook-api.mjs";
|
|
22
22
|
|
|
23
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
24
|
const __dirname = path.dirname(__filename);
|
|
@@ -95,6 +95,11 @@ const runtime = {
|
|
|
95
95
|
pairingAttemptsByRemoteAddress: new Map(),
|
|
96
96
|
ipcClient: null,
|
|
97
97
|
stopping: false,
|
|
98
|
+
// In-memory cache for the `/api/share/status` endpoint. A 30s TTL avoids
|
|
99
|
+
// hammering the share worker on every settings-page render. Purely runtime —
|
|
100
|
+
// deliberately NOT persisted via `state` since the cache is worthless after
|
|
101
|
+
// a restart and would just bloat the state file.
|
|
102
|
+
a2aShareStatusCache: null,
|
|
98
103
|
};
|
|
99
104
|
const state = await loadState(config.stateFile);
|
|
100
105
|
runtime.threadRegistry = await loadThreadRegistry(config.threadRegistryFile);
|
|
@@ -158,6 +163,7 @@ state.recentHistoryItems = initialHistoryItems;
|
|
|
158
163
|
state.recentTimelineEntries = initialTimelineEntries;
|
|
159
164
|
const migratedRecentCodeEventsStateChanged = migrateRecentCodeEventsState({ config, runtime, state });
|
|
160
165
|
const restoredTimelineImagePathsStateChanged = await backfillPersistedTimelineImagePaths({ config, runtime, state });
|
|
166
|
+
const backfilledMoltbookInboxChanged = await backfillMoltbookInboxHistory({ config, runtime, state });
|
|
161
167
|
runtime.historyFileState.offset = Number(state.historyFileOffset) || 0;
|
|
162
168
|
runtime.historyFileState.sourceFile = cleanText(state.historyFileSourceFile ?? "");
|
|
163
169
|
|
|
@@ -269,6 +275,9 @@ function buildSessionLocalePayload(config, state, deviceId) {
|
|
|
269
275
|
moltbookEnabled: Boolean(config.moltbookApiKey),
|
|
270
276
|
a2aEnabled: Boolean(config.a2aApiKey),
|
|
271
277
|
a2aRelayEnabled: Boolean(config.a2aRelayUrl && config.a2aRelayUserId),
|
|
278
|
+
// Share piggy-backs on A2A credentials — it's "enabled" as soon as both
|
|
279
|
+
// halves (user id + API key) are provisioned.
|
|
280
|
+
a2aShareEnabled: Boolean(config.a2aRelayUserId && config.a2aApiKey),
|
|
272
281
|
a2aExecutors: runtime.a2aAvailableExecutors || { codex: false, claude: false },
|
|
273
282
|
a2aExecutorPreference: state.a2aExecutorPreference || "ask",
|
|
274
283
|
};
|
|
@@ -2549,6 +2558,26 @@ function recordHistoryItem({ config, runtime, state, item }) {
|
|
|
2549
2558
|
return changed;
|
|
2550
2559
|
}
|
|
2551
2560
|
|
|
2561
|
+
// Flip a previously-recorded history item's readOnly flag without disturbing
|
|
2562
|
+
// its ordering or other fields. Used by moltbook_reply / moltbook_draft flows
|
|
2563
|
+
// to transition pending → resolved so the entry starts matching the completed
|
|
2564
|
+
// tab's readOnly filter. Returns true if a mutation happened (caller should
|
|
2565
|
+
// saveState in that case).
|
|
2566
|
+
function flipHistoryItemReadOnly({ runtime, state, stableId, readOnly = true }) {
|
|
2567
|
+
const normalizedStableId = cleanText(stableId || "");
|
|
2568
|
+
if (!normalizedStableId) return false;
|
|
2569
|
+
const idx = runtime.recentHistoryItems.findIndex((entry) => entry.stableId === normalizedStableId);
|
|
2570
|
+
if (idx === -1) return false;
|
|
2571
|
+
const current = runtime.recentHistoryItems[idx];
|
|
2572
|
+
if (current.readOnly === readOnly) return false;
|
|
2573
|
+
const next = { ...current, readOnly };
|
|
2574
|
+
const nextItems = runtime.recentHistoryItems.slice();
|
|
2575
|
+
nextItems[idx] = next;
|
|
2576
|
+
runtime.recentHistoryItems = nextItems;
|
|
2577
|
+
state.recentHistoryItems = nextItems;
|
|
2578
|
+
return true;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2552
2581
|
function recordActionHistoryItem({
|
|
2553
2582
|
config,
|
|
2554
2583
|
runtime,
|
|
@@ -3760,6 +3789,66 @@ async function backfillPersistedTimelineImagePaths({ config, runtime, state }) {
|
|
|
3760
3789
|
return true;
|
|
3761
3790
|
}
|
|
3762
3791
|
|
|
3792
|
+
// Walk ~/.viveworker/moltbook-inbox/ on startup and synthesize readOnly
|
|
3793
|
+
// history entries for items the CLI / reconcile marked as replied or skipped.
|
|
3794
|
+
// Without this, an inbox item that was handled before the history hooks
|
|
3795
|
+
// existed — or that was resolved purely via the CLI path, which doesn't touch
|
|
3796
|
+
// the bridge's in-memory maps — never makes it into `recentHistoryItems` and
|
|
3797
|
+
// so never appears in the completed tab. We match the same stableId / token
|
|
3798
|
+
// scheme as the live push (sourceId = `comment:<commentId>`) so a future
|
|
3799
|
+
// watcher push for the same commentId replaces this entry cleanly rather than
|
|
3800
|
+
// creating a duplicate.
|
|
3801
|
+
async function backfillMoltbookInboxHistory({ config, runtime, state }) {
|
|
3802
|
+
let changed = false;
|
|
3803
|
+
try {
|
|
3804
|
+
const inboxItems = await listInboxItems();
|
|
3805
|
+
for (const inbox of inboxItems) {
|
|
3806
|
+
const status = String(inbox?.status || "").toLowerCase();
|
|
3807
|
+
if (status !== "replied" && status !== "skipped") continue;
|
|
3808
|
+
const commentId = String(inbox?.commentId || "");
|
|
3809
|
+
if (!commentId) continue;
|
|
3810
|
+
const sourceId = `comment:${commentId}`;
|
|
3811
|
+
const stableId = `moltbook_reply:${sourceId}`;
|
|
3812
|
+
if (runtime.recentHistoryItems.some((entry) => entry.stableId === stableId)) continue;
|
|
3813
|
+
const token = historyToken(`moltbook:${sourceId}`);
|
|
3814
|
+
const createdAtMs = Number(Date.parse(inbox.updatedAt || inbox.createdAt || "")) || Date.now();
|
|
3815
|
+
const postTitle = cleanText(inbox.postTitle || "");
|
|
3816
|
+
const contextText = cleanText(inbox.contextText || "");
|
|
3817
|
+
const replyText = cleanText(inbox.replyText || "");
|
|
3818
|
+
const messageText = status === "replied" ? (replyText || contextText) : contextText;
|
|
3819
|
+
const summary = (status === "replied" ? (replyText || contextText) : contextText).slice(0, 160);
|
|
3820
|
+
const titlePrefix = status === "replied" ? "replied" : "skipped";
|
|
3821
|
+
const title = postTitle ? `${titlePrefix} → ${postTitle}` : (status === "replied" ? "Moltbook reply" : "Moltbook skipped");
|
|
3822
|
+
const recorded = recordHistoryItem({
|
|
3823
|
+
config,
|
|
3824
|
+
runtime,
|
|
3825
|
+
state,
|
|
3826
|
+
item: {
|
|
3827
|
+
stableId,
|
|
3828
|
+
token,
|
|
3829
|
+
kind: "moltbook_reply",
|
|
3830
|
+
threadId: "moltbook",
|
|
3831
|
+
threadLabel: postTitle || "Moltbook",
|
|
3832
|
+
title,
|
|
3833
|
+
summary,
|
|
3834
|
+
messageText,
|
|
3835
|
+
createdAtMs,
|
|
3836
|
+
readOnly: true,
|
|
3837
|
+
provider: "moltbook",
|
|
3838
|
+
},
|
|
3839
|
+
});
|
|
3840
|
+
if (recorded) changed = true;
|
|
3841
|
+
}
|
|
3842
|
+
} catch (error) {
|
|
3843
|
+
console.error(`[moltbook-inbox-backfill] ${error.message}`);
|
|
3844
|
+
return false;
|
|
3845
|
+
}
|
|
3846
|
+
if (changed) {
|
|
3847
|
+
console.log(`[moltbook-inbox-backfill] Synthesized history entries for resolved inbox items`);
|
|
3848
|
+
}
|
|
3849
|
+
return changed;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3763
3852
|
async function backfillRecentTimelineEntryDiffs({ config, runtime, state }) {
|
|
3764
3853
|
const nextEntries = runtime.recentTimelineEntries.map((entry) => ({ ...entry }));
|
|
3765
3854
|
let changed = false;
|
|
@@ -9266,8 +9355,11 @@ function buildCompletedInboxItems(runtime, state, config, locale) {
|
|
|
9266
9355
|
.filter((item) => {
|
|
9267
9356
|
const k = cleanText(item?.kind || "");
|
|
9268
9357
|
if (!completedKinds.has(k)) return false;
|
|
9269
|
-
// Only resolved approvals (readOnly = true)
|
|
9270
|
-
|
|
9358
|
+
// Only resolved approvals (readOnly = true). Moltbook reply/draft items
|
|
9359
|
+
// are gated the same way so pending entries don't double-show as
|
|
9360
|
+
// completed while they're also surfaced via moltbookItemsByToken /
|
|
9361
|
+
// moltbookDraftsByToken on the pending list.
|
|
9362
|
+
if ((k === "approval" || k === "moltbook_reply" || k === "moltbook_draft") && !item.readOnly) return false;
|
|
9271
9363
|
return true;
|
|
9272
9364
|
})
|
|
9273
9365
|
.slice()
|
|
@@ -11429,7 +11521,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11429
11521
|
return writeJson(res, 200, { enabled: false });
|
|
11430
11522
|
}
|
|
11431
11523
|
try {
|
|
11432
|
-
const { readScoutState, rollScoutDayIfNeeded } = await import("./moltbook-api.mjs");
|
|
11524
|
+
const { readScoutState, rollScoutDayIfNeeded, createMoltbookClient } = await import("./moltbook-api.mjs");
|
|
11433
11525
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
11434
11526
|
const batch = scoutState.batch;
|
|
11435
11527
|
const batchInfo = batch && batch.candidates?.length ? {
|
|
@@ -11438,8 +11530,39 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11438
11530
|
topScore: Math.max(...batch.candidates.map((c) => c.score || 0)),
|
|
11439
11531
|
remainingSeconds: Math.max(0, Math.round(((batch.startedAt || 0) + (batch.windowMs || 1800000) - Date.now()) / 1000)),
|
|
11440
11532
|
} : null;
|
|
11533
|
+
|
|
11534
|
+
// Agent profile — lazy-fetched, cached 1h. Stale cache survives
|
|
11535
|
+
// fetch failures so the UI never goes blank mid-outage.
|
|
11536
|
+
let account = null;
|
|
11537
|
+
try {
|
|
11538
|
+
const PROFILE_TTL_MS = 60 * 60 * 1000;
|
|
11539
|
+
const cached = runtime.moltbookAgentProfile;
|
|
11540
|
+
const nowMs = Date.now();
|
|
11541
|
+
if (!cached || nowMs - (cached.fetchedAtMs || 0) > PROFILE_TTL_MS) {
|
|
11542
|
+
const mb = createMoltbookClient(config.moltbookApiKey);
|
|
11543
|
+
const meRes = await mb("/agents/me");
|
|
11544
|
+
const name = cleanText(meRes?.agent?.name || meRes?.agent?.display_name || "");
|
|
11545
|
+
if (name) {
|
|
11546
|
+
runtime.moltbookAgentProfile = {
|
|
11547
|
+
name,
|
|
11548
|
+
profileUrl: `https://www.moltbook.com/u/${encodeURIComponent(name)}`,
|
|
11549
|
+
fetchedAtMs: nowMs,
|
|
11550
|
+
};
|
|
11551
|
+
}
|
|
11552
|
+
}
|
|
11553
|
+
} catch {
|
|
11554
|
+
// Fall through — if fetch fails, use whatever's cached (possibly null).
|
|
11555
|
+
}
|
|
11556
|
+
if (runtime.moltbookAgentProfile) {
|
|
11557
|
+
account = {
|
|
11558
|
+
name: runtime.moltbookAgentProfile.name,
|
|
11559
|
+
profileUrl: runtime.moltbookAgentProfile.profileUrl,
|
|
11560
|
+
};
|
|
11561
|
+
}
|
|
11562
|
+
|
|
11441
11563
|
return writeJson(res, 200, {
|
|
11442
11564
|
enabled: true,
|
|
11565
|
+
account,
|
|
11443
11566
|
day: scoutState.day,
|
|
11444
11567
|
sentToday: scoutState.sentToday,
|
|
11445
11568
|
maxDaily: 5,
|
|
@@ -11478,6 +11601,82 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11478
11601
|
});
|
|
11479
11602
|
}
|
|
11480
11603
|
|
|
11604
|
+
// A2A Share status (HTML hosting) for the settings UI. Reuses A2A
|
|
11605
|
+
// credentials; enabled iff both user id + API key are provisioned.
|
|
11606
|
+
// Results from the upstream `/api/list` call are cached for 30s so a
|
|
11607
|
+
// settings-page render doesn't hammer the worker on every refresh tick.
|
|
11608
|
+
if (url.pathname === "/api/share/status" && req.method === "GET") {
|
|
11609
|
+
const session = requireApiSession(req, res, config, state);
|
|
11610
|
+
if (!session) return;
|
|
11611
|
+
const enabled = Boolean(config.a2aRelayUserId && config.a2aApiKey);
|
|
11612
|
+
if (!enabled) {
|
|
11613
|
+
return writeJson(res, 200, { enabled: false });
|
|
11614
|
+
}
|
|
11615
|
+
const shareHost = (() => {
|
|
11616
|
+
try { return new URL(config.a2aShareUrl).host; } catch { return config.a2aShareUrl || ""; }
|
|
11617
|
+
})();
|
|
11618
|
+
// Mirror the constants baked into share-worker/worker.js. If those
|
|
11619
|
+
// change, bump here too — there's no shared module between the two.
|
|
11620
|
+
const limits = {
|
|
11621
|
+
maxFileBytes: 5 * 1024 * 1024,
|
|
11622
|
+
maxTotalBytes: 5 * 1024 * 1024,
|
|
11623
|
+
maxFiles: 10,
|
|
11624
|
+
defaultExpiresDays: 30,
|
|
11625
|
+
maxExpiresDays: 30,
|
|
11626
|
+
uploadRatePerHour: 10,
|
|
11627
|
+
};
|
|
11628
|
+
const cacheKey = `${config.a2aRelayUserId}|${config.a2aShareUrl}`;
|
|
11629
|
+
const nowMs = Date.now();
|
|
11630
|
+
const cached = runtime.a2aShareStatusCache;
|
|
11631
|
+
if (cached && cached.key === cacheKey && nowMs - cached.fetchedAtMs < 30_000) {
|
|
11632
|
+
return writeJson(res, 200, {
|
|
11633
|
+
enabled: true,
|
|
11634
|
+
userId: config.a2aRelayUserId,
|
|
11635
|
+
shareUrl: config.a2aShareUrl,
|
|
11636
|
+
shareHost,
|
|
11637
|
+
limits,
|
|
11638
|
+
...cached.payload,
|
|
11639
|
+
fetchedAtMs: cached.fetchedAtMs,
|
|
11640
|
+
});
|
|
11641
|
+
}
|
|
11642
|
+
let upstream = { items: [], quota: null, error: null };
|
|
11643
|
+
const controller = new AbortController();
|
|
11644
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
11645
|
+
try {
|
|
11646
|
+
const resUpstream = await fetch(`${config.a2aShareUrl}/api/list`, {
|
|
11647
|
+
method: "GET",
|
|
11648
|
+
headers: {
|
|
11649
|
+
"x-a2a-user": config.a2aRelayUserId,
|
|
11650
|
+
"x-a2a-key": config.a2aApiKey,
|
|
11651
|
+
},
|
|
11652
|
+
signal: controller.signal,
|
|
11653
|
+
});
|
|
11654
|
+
if (resUpstream.ok) {
|
|
11655
|
+
const body = await resUpstream.json().catch(() => ({}));
|
|
11656
|
+
upstream.items = Array.isArray(body.items) ? body.items : [];
|
|
11657
|
+
upstream.quota = body.quota || null;
|
|
11658
|
+
} else {
|
|
11659
|
+
upstream.error = `HTTP ${resUpstream.status}`;
|
|
11660
|
+
}
|
|
11661
|
+
} catch (error) {
|
|
11662
|
+
upstream.error = error?.name === "AbortError"
|
|
11663
|
+
? "upstream timeout"
|
|
11664
|
+
: (error?.message || String(error));
|
|
11665
|
+
} finally {
|
|
11666
|
+
clearTimeout(timer);
|
|
11667
|
+
}
|
|
11668
|
+
runtime.a2aShareStatusCache = { key: cacheKey, fetchedAtMs: nowMs, payload: upstream };
|
|
11669
|
+
return writeJson(res, 200, {
|
|
11670
|
+
enabled: true,
|
|
11671
|
+
userId: config.a2aRelayUserId,
|
|
11672
|
+
shareUrl: config.a2aShareUrl,
|
|
11673
|
+
shareHost,
|
|
11674
|
+
limits,
|
|
11675
|
+
...upstream,
|
|
11676
|
+
fetchedAtMs: nowMs,
|
|
11677
|
+
});
|
|
11678
|
+
}
|
|
11679
|
+
|
|
11481
11680
|
// Toggle acceptPublicTasks on the A2A relay.
|
|
11482
11681
|
if (url.pathname === "/api/a2a/public-tasks" && req.method === "POST") {
|
|
11483
11682
|
const session = requireApiSession(req, res, config, state);
|
|
@@ -12247,6 +12446,14 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
12247
12446
|
if (eventType === "resolve") {
|
|
12248
12447
|
const token = historyToken(`moltbook:${sourceId}`);
|
|
12249
12448
|
runtime.moltbookItemsByToken.delete(token);
|
|
12449
|
+
// Flip the history item to readOnly so it moves from pending → completed.
|
|
12450
|
+
try {
|
|
12451
|
+
if (flipHistoryItemReadOnly({ runtime, state, stableId: `moltbook_reply:${sourceId}` })) {
|
|
12452
|
+
await saveState(config.stateFile, state);
|
|
12453
|
+
}
|
|
12454
|
+
} catch (error) {
|
|
12455
|
+
console.error(`[moltbook-reply-resolve] ${error.message}`);
|
|
12456
|
+
}
|
|
12250
12457
|
return writeJson(res, 200, { ok: true });
|
|
12251
12458
|
}
|
|
12252
12459
|
const token = historyToken(`moltbook:${sourceId}`);
|
|
@@ -12268,24 +12475,21 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
12268
12475
|
};
|
|
12269
12476
|
runtime.moltbookItemsByToken.set(token, item);
|
|
12270
12477
|
try {
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
provider: "moltbook",
|
|
12287
|
-
},
|
|
12288
|
-
});
|
|
12478
|
+
const historyEntry = {
|
|
12479
|
+
stableId: `moltbook_reply:${sourceId}`,
|
|
12480
|
+
token,
|
|
12481
|
+
kind: "moltbook_reply",
|
|
12482
|
+
threadId: item.threadId,
|
|
12483
|
+
threadLabel: item.threadLabel,
|
|
12484
|
+
title: item.title,
|
|
12485
|
+
summary: item.summary,
|
|
12486
|
+
messageText: item.contextText,
|
|
12487
|
+
createdAtMs: item.createdAtMs,
|
|
12488
|
+
readOnly: false,
|
|
12489
|
+
provider: "moltbook",
|
|
12490
|
+
};
|
|
12491
|
+
recordTimelineEntry({ config, runtime, state, entry: historyEntry });
|
|
12492
|
+
recordHistoryItem({ config, runtime, state, item: historyEntry });
|
|
12289
12493
|
await saveState(config.stateFile, state);
|
|
12290
12494
|
} catch (error) {
|
|
12291
12495
|
console.error(`[moltbook-timeline-save] ${error.message}`);
|
|
@@ -12351,6 +12555,13 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
12351
12555
|
}
|
|
12352
12556
|
item.resolved = true;
|
|
12353
12557
|
runtime.moltbookItemsByToken.delete(token);
|
|
12558
|
+
try {
|
|
12559
|
+
if (flipHistoryItemReadOnly({ runtime, state, stableId: `moltbook_reply:${item.sourceId}` })) {
|
|
12560
|
+
await saveState(config.stateFile, state);
|
|
12561
|
+
}
|
|
12562
|
+
} catch (error) {
|
|
12563
|
+
console.error(`[moltbook-reply-phone] ${error.message}`);
|
|
12564
|
+
}
|
|
12354
12565
|
return writeJson(res, 200, { ok: true, action });
|
|
12355
12566
|
}
|
|
12356
12567
|
|
|
@@ -12419,36 +12630,33 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
12419
12630
|
runtime.moltbookDraftsByToken.set(token, draft);
|
|
12420
12631
|
await writeDraft(draft).catch((e) => console.error(`[moltbook-draft-persist] ${e.message}`));
|
|
12421
12632
|
try {
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
provider: "moltbook",
|
|
12450
|
-
},
|
|
12451
|
-
});
|
|
12633
|
+
const draftEntry = {
|
|
12634
|
+
stableId: `moltbook_draft:${sourceId}`,
|
|
12635
|
+
token,
|
|
12636
|
+
kind: "moltbook_draft",
|
|
12637
|
+
threadId: "moltbook",
|
|
12638
|
+
threadLabel: postTitle || "Moltbook",
|
|
12639
|
+
title: draftType === "original_post"
|
|
12640
|
+
? (postTitle || "Moltbook new post")
|
|
12641
|
+
: (postTitle ? `draft → ${postTitle}` : "Moltbook draft"),
|
|
12642
|
+
summary: contextSummary || String(draftText).slice(0, 160),
|
|
12643
|
+
messageText: contextSummary || draftText,
|
|
12644
|
+
draftText,
|
|
12645
|
+
draftType,
|
|
12646
|
+
submoltName,
|
|
12647
|
+
intent,
|
|
12648
|
+
slot,
|
|
12649
|
+
postId,
|
|
12650
|
+
postUrl,
|
|
12651
|
+
postTitle,
|
|
12652
|
+
postAuthor,
|
|
12653
|
+
postBody,
|
|
12654
|
+
createdAtMs: draft.createdAtMs,
|
|
12655
|
+
readOnly: false,
|
|
12656
|
+
provider: "moltbook",
|
|
12657
|
+
};
|
|
12658
|
+
recordTimelineEntry({ config, runtime, state, entry: draftEntry });
|
|
12659
|
+
recordHistoryItem({ config, runtime, state, item: draftEntry });
|
|
12452
12660
|
await saveState(config.stateFile, state);
|
|
12453
12661
|
} catch (error) {
|
|
12454
12662
|
console.error(`[moltbook-draft-timeline-save] ${error.message}`);
|
|
@@ -12549,6 +12757,15 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
12549
12757
|
// Persist decision to disk.
|
|
12550
12758
|
await writeDraft(draft).catch((e) => console.error(`[moltbook-draft-persist-decision] ${e.message}`));
|
|
12551
12759
|
|
|
12760
|
+
// Flip the history entry to readOnly so it moves from pending → completed.
|
|
12761
|
+
try {
|
|
12762
|
+
if (flipHistoryItemReadOnly({ runtime, state, stableId: `moltbook_draft:${draft.sourceId}` })) {
|
|
12763
|
+
await saveState(config.stateFile, state);
|
|
12764
|
+
}
|
|
12765
|
+
} catch (error) {
|
|
12766
|
+
console.error(`[moltbook-draft-decision-flip] ${error.message}`);
|
|
12767
|
+
}
|
|
12768
|
+
|
|
12552
12769
|
if (action === "approve") {
|
|
12553
12770
|
// Execute posting asynchronously — fire-and-forget from the HTTP handler.
|
|
12554
12771
|
(async () => {
|
|
@@ -14564,6 +14781,10 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
14564
14781
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
14565
14782
|
scoutState.sentToday += 1;
|
|
14566
14783
|
markPostSeen(scoutState, draft.postId, "published");
|
|
14784
|
+
// Append the reply to `recentComposeTitles` so it shows up in the
|
|
14785
|
+
// "最近の投稿" list with its reply badge. `recordComposeAttempt` knows
|
|
14786
|
+
// not to bump `composedToday` when type === "reply", so the
|
|
14787
|
+
// "本日の新規投稿数" counter only reflects original posts.
|
|
14567
14788
|
recordComposeAttempt(scoutState, draft.postTitle || draft.postId, draft.postId, "reply");
|
|
14568
14789
|
await writeScoutState(scoutState);
|
|
14569
14790
|
}
|
|
@@ -14638,6 +14859,11 @@ function buildConfig(cli) {
|
|
|
14638
14859
|
a2aRelayUserId: cleanText(process.env.A2A_RELAY_USER_ID || ""),
|
|
14639
14860
|
a2aRelaySecret: cleanText(process.env.A2A_RELAY_SECRET || ""),
|
|
14640
14861
|
a2aRelayRegisterSecret: cleanText(process.env.A2A_RELAY_REGISTER_SECRET || ""),
|
|
14862
|
+
// share.viveworker.com — HTML hosting backed by the same A2A credentials.
|
|
14863
|
+
// Override for staging / self-hosted deployments via VIVEWORKER_SHARE_URL.
|
|
14864
|
+
a2aShareUrl: stripTrailingSlash(
|
|
14865
|
+
process.env.VIVEWORKER_SHARE_URL || "https://share.viveworker.com"
|
|
14866
|
+
),
|
|
14641
14867
|
webUiEnabled: boolEnv("WEB_UI_ENABLED", true),
|
|
14642
14868
|
authRequired: boolEnv("AUTH_REQUIRED", true),
|
|
14643
14869
|
webPushEnabled: boolEnv("WEB_PUSH_ENABLED", false),
|
|
@@ -15989,6 +16215,7 @@ async function main() {
|
|
|
15989
16215
|
restoredTimelineImagePathsStateChanged ||
|
|
15990
16216
|
migratedRecentCodeEventsStateChanged ||
|
|
15991
16217
|
restoredPendingUserInputStateChanged ||
|
|
16218
|
+
backfilledMoltbookInboxChanged ||
|
|
15992
16219
|
refreshResolvedThreadLabels({ config, runtime, state })
|
|
15993
16220
|
) {
|
|
15994
16221
|
await saveState(config.stateFile, state);
|
package/web/app.css
CHANGED
|
@@ -610,14 +610,16 @@ code {
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
.settings-compose-badge {
|
|
613
|
-
display: inline-
|
|
613
|
+
display: inline-flex;
|
|
614
614
|
align-self: flex-start;
|
|
615
|
+
flex: 0 0 auto;
|
|
615
616
|
padding: 0.12rem 0.42rem;
|
|
616
617
|
border-radius: 6px;
|
|
617
618
|
font-size: 0.7rem;
|
|
618
619
|
font-weight: 600;
|
|
619
620
|
letter-spacing: 0.03em;
|
|
620
621
|
line-height: 1.3;
|
|
622
|
+
white-space: nowrap;
|
|
621
623
|
}
|
|
622
624
|
|
|
623
625
|
.settings-compose-badge--post {
|
|
@@ -637,6 +639,78 @@ code {
|
|
|
637
639
|
padding: 0.72rem 1rem;
|
|
638
640
|
}
|
|
639
641
|
|
|
642
|
+
.settings-icon-entry,
|
|
643
|
+
.settings-share-file-entry {
|
|
644
|
+
display: grid;
|
|
645
|
+
grid-template-columns: 1.65rem minmax(0, 1fr);
|
|
646
|
+
align-items: flex-start;
|
|
647
|
+
gap: 0.62rem;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.settings-icon-entry__icon,
|
|
651
|
+
.settings-share-file-entry__icon {
|
|
652
|
+
display: inline-flex;
|
|
653
|
+
align-items: center;
|
|
654
|
+
justify-content: center;
|
|
655
|
+
width: 1.65rem;
|
|
656
|
+
height: 1.65rem;
|
|
657
|
+
border-radius: 10px;
|
|
658
|
+
color: rgba(121, 196, 255, 0.9);
|
|
659
|
+
background: rgba(121, 196, 255, 0.11);
|
|
660
|
+
box-shadow: inset 0 0 0 1px rgba(121, 196, 255, 0.14);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.settings-icon-entry__icon--post {
|
|
664
|
+
color: rgba(121, 196, 255, 0.9);
|
|
665
|
+
background: rgba(121, 196, 255, 0.11);
|
|
666
|
+
box-shadow: inset 0 0 0 1px rgba(121, 196, 255, 0.14);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.settings-icon-entry__icon--reply {
|
|
670
|
+
color: rgba(190, 160, 255, 0.92);
|
|
671
|
+
background: rgba(160, 120, 255, 0.13);
|
|
672
|
+
box-shadow: inset 0 0 0 1px rgba(160, 120, 255, 0.16);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.settings-icon-entry__icon--file {
|
|
676
|
+
color: rgba(121, 196, 255, 0.9);
|
|
677
|
+
background: rgba(121, 196, 255, 0.11);
|
|
678
|
+
box-shadow: inset 0 0 0 1px rgba(121, 196, 255, 0.14);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.settings-icon-entry__icon svg,
|
|
682
|
+
.settings-share-file-entry__icon svg {
|
|
683
|
+
width: 0.95rem;
|
|
684
|
+
height: 0.95rem;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.settings-icon-entry__body,
|
|
688
|
+
.settings-share-file-entry__body {
|
|
689
|
+
min-width: 0;
|
|
690
|
+
display: grid;
|
|
691
|
+
gap: 0.22rem;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.settings-icon-entry__title-row,
|
|
695
|
+
.settings-share-file-entry__title-row {
|
|
696
|
+
display: flex;
|
|
697
|
+
align-items: center;
|
|
698
|
+
gap: 0.38rem;
|
|
699
|
+
min-width: 0;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.settings-icon-entry__title-row .settings-compose-entry__title,
|
|
703
|
+
.settings-share-file-entry__title-row .settings-compose-entry__title {
|
|
704
|
+
flex: 1 1 auto;
|
|
705
|
+
min-width: 0;
|
|
706
|
+
overflow-wrap: anywhere;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.settings-icon-entry__title-row .settings-compose-badge,
|
|
710
|
+
.settings-share-file-entry__title-row .settings-compose-badge {
|
|
711
|
+
margin-left: auto;
|
|
712
|
+
}
|
|
713
|
+
|
|
640
714
|
.settings-compose-entry + .settings-compose-entry {
|
|
641
715
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
642
716
|
}
|
package/web/app.js
CHANGED
|
@@ -50,6 +50,8 @@ const state = {
|
|
|
50
50
|
moltbookScoutStatus: null,
|
|
51
51
|
moltbookRecentTitlesExpanded: 0,
|
|
52
52
|
a2aRelayStatus: null,
|
|
53
|
+
a2aShareStatus: null,
|
|
54
|
+
a2aShareRecentExpanded: 0,
|
|
53
55
|
a2aTaskExecutorPick: "codex",
|
|
54
56
|
pushNotice: "",
|
|
55
57
|
pushError: "",
|
|
@@ -218,6 +220,7 @@ async function refreshAuthenticatedState() {
|
|
|
218
220
|
await refreshPushStatus();
|
|
219
221
|
await fetchMoltbookScoutStatus();
|
|
220
222
|
await fetchA2aRelayStatus();
|
|
223
|
+
await fetchA2aShareStatus();
|
|
221
224
|
ensureCurrentSelection();
|
|
222
225
|
}
|
|
223
226
|
|
|
@@ -347,6 +350,18 @@ async function fetchA2aRelayStatus() {
|
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
|
|
353
|
+
async function fetchA2aShareStatus() {
|
|
354
|
+
if (!state.session?.a2aShareEnabled) {
|
|
355
|
+
state.a2aShareStatus = null;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
state.a2aShareStatus = await apiGet("/api/share/status");
|
|
360
|
+
} catch {
|
|
361
|
+
state.a2aShareStatus = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
350
365
|
async function getClientPushState() {
|
|
351
366
|
const registration = state.serviceWorkerRegistration || (await navigator.serviceWorker?.ready.catch(() => null));
|
|
352
367
|
if (registration) {
|
|
@@ -2865,6 +2880,7 @@ function buildSettingsContext() {
|
|
|
2865
2880
|
}),
|
|
2866
2881
|
moltbookScout: state.moltbookScoutStatus,
|
|
2867
2882
|
a2aRelay: state.a2aRelayStatus,
|
|
2883
|
+
a2aShare: state.a2aShareStatus,
|
|
2868
2884
|
};
|
|
2869
2885
|
}
|
|
2870
2886
|
|
|
@@ -3025,6 +3041,13 @@ function settingsPageMeta(page) {
|
|
|
3025
3041
|
description: L("settings.a2aRelay.copy"),
|
|
3026
3042
|
icon: "link",
|
|
3027
3043
|
};
|
|
3044
|
+
case "a2aShare":
|
|
3045
|
+
return {
|
|
3046
|
+
id: "a2aShare",
|
|
3047
|
+
title: L("settings.a2aShare.title"),
|
|
3048
|
+
description: L("settings.a2aShare.copy"),
|
|
3049
|
+
icon: "link",
|
|
3050
|
+
};
|
|
3028
3051
|
case "a2aExecutor":
|
|
3029
3052
|
// Executor settings integrated into a2aRelay page — redirect.
|
|
3030
3053
|
return settingsPageMeta("a2aRelay");
|
|
@@ -3097,13 +3120,13 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
3097
3120
|
`
|
|
3098
3121
|
}
|
|
3099
3122
|
${renderSettingsGroup(L("settings.group.general"), generalRows)}
|
|
3100
|
-
${(state.session?.moltbookEnabled || state.session?.a2aRelayEnabled) ? renderSettingsGroup(L("settings.group.integrations"), [
|
|
3123
|
+
${(state.session?.moltbookEnabled || state.session?.a2aRelayEnabled || state.session?.a2aShareEnabled) ? renderSettingsGroup(L("settings.group.integrations"), [
|
|
3101
3124
|
state.session?.moltbookEnabled ? renderSettingsNavRow({
|
|
3102
3125
|
page: "moltbook",
|
|
3103
3126
|
icon: "item",
|
|
3104
3127
|
title: L("settings.moltbook.title"),
|
|
3105
3128
|
subtitle: L("settings.moltbook.subtitle"),
|
|
3106
|
-
value: context.moltbookScout?.enabled ?
|
|
3129
|
+
value: context.moltbookScout?.enabled ? L("settings.status.enabled") : L("settings.status.disabled"),
|
|
3107
3130
|
}) : "",
|
|
3108
3131
|
state.session?.a2aRelayEnabled ? renderSettingsNavRow({
|
|
3109
3132
|
page: "a2aRelay",
|
|
@@ -3112,6 +3135,13 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
3112
3135
|
subtitle: L("settings.a2aRelay.subtitle"),
|
|
3113
3136
|
value: context.a2aRelay?.connected ? L("settings.status.connected") : L("settings.a2aRelay.status.disconnected"),
|
|
3114
3137
|
}) : "",
|
|
3138
|
+
state.session?.a2aShareEnabled ? renderSettingsNavRow({
|
|
3139
|
+
page: "a2aShare",
|
|
3140
|
+
icon: "link",
|
|
3141
|
+
title: L("settings.a2aShare.title"),
|
|
3142
|
+
subtitle: L("settings.a2aShare.subtitle"),
|
|
3143
|
+
value: context.a2aShare?.enabled ? L("settings.status.enabled") : L("settings.status.disabled"),
|
|
3144
|
+
}) : "",
|
|
3115
3145
|
].filter(Boolean)) : ""}
|
|
3116
3146
|
${renderSettingsGroup(L("settings.pairing.title"), deviceRows)}
|
|
3117
3147
|
${renderSettingsGroup(L("settings.group.advanced"), advancedRows)}
|
|
@@ -3163,6 +3193,9 @@ function renderSettingsSubpage(context, { mobile }) {
|
|
|
3163
3193
|
case "a2aExecutor":
|
|
3164
3194
|
content = renderSettingsA2aRelayPage(context);
|
|
3165
3195
|
break;
|
|
3196
|
+
case "a2aShare":
|
|
3197
|
+
content = renderSettingsA2aSharePage(context);
|
|
3198
|
+
break;
|
|
3166
3199
|
default:
|
|
3167
3200
|
content = "";
|
|
3168
3201
|
}
|
|
@@ -3392,8 +3425,16 @@ function renderSettingsMoltbookPage(context) {
|
|
|
3392
3425
|
renderSettingsInfoRow(L("settings.row.moltbookBatchTopScore"), String(scout.batch.topScore)),
|
|
3393
3426
|
renderSettingsInfoRow(L("settings.row.moltbookBatchRemaining"), `${Math.floor(scout.batch.remainingSeconds / 60)}:${String(scout.batch.remainingSeconds % 60).padStart(2, "0")}`),
|
|
3394
3427
|
] : [];
|
|
3428
|
+
const accountRow = scout.account?.name && scout.account?.profileUrl
|
|
3429
|
+
? renderSettingsInfoRow(
|
|
3430
|
+
L("settings.row.moltbookAccount"),
|
|
3431
|
+
`<a href="${escapeHtml(scout.account.profileUrl)}" target="_blank" rel="noopener">${escapeHtml(scout.account.name)}</a>`,
|
|
3432
|
+
{ rawValue: true }
|
|
3433
|
+
)
|
|
3434
|
+
: null;
|
|
3395
3435
|
return `
|
|
3396
3436
|
<div class="settings-page">
|
|
3437
|
+
${accountRow ? renderSettingsGroup("", [accountRow]) : ""}
|
|
3397
3438
|
${renderSettingsGroup("", [
|
|
3398
3439
|
renderSettingsInfoRow(L("settings.row.moltbookQuota"), `${scout.sentToday} / ${scout.maxDaily}`),
|
|
3399
3440
|
renderSettingsInfoRow(L("settings.row.moltbookComposed"), `${scout.composedToday || 0} / 3`),
|
|
@@ -3417,7 +3458,19 @@ function renderSettingsMoltbookPage(context) {
|
|
|
3417
3458
|
const link = postId
|
|
3418
3459
|
? `<a href="https://www.moltbook.com/post/${escapeHtml(postId)}" target="_blank" rel="noopener">${escapeHtml(title)}</a>`
|
|
3419
3460
|
: escapeHtml(title);
|
|
3420
|
-
|
|
3461
|
+
const iconName = type === "reply" ? "moltbook-reply" : "moltbook-draft";
|
|
3462
|
+
const iconTone = type === "reply" ? "settings-icon-entry__icon--reply" : "settings-icon-entry__icon--post";
|
|
3463
|
+
return `
|
|
3464
|
+
<div class="settings-compose-entry settings-icon-entry">
|
|
3465
|
+
<span class="settings-icon-entry__icon ${iconTone}" aria-hidden="true">${renderIcon(iconName)}</span>
|
|
3466
|
+
<span class="settings-icon-entry__body">
|
|
3467
|
+
<span class="settings-icon-entry__title-row">
|
|
3468
|
+
<span class="settings-compose-entry__title">${link}</span>
|
|
3469
|
+
${badge}
|
|
3470
|
+
</span>
|
|
3471
|
+
</span>
|
|
3472
|
+
</div>
|
|
3473
|
+
`;
|
|
3421
3474
|
});
|
|
3422
3475
|
if (hasMore) {
|
|
3423
3476
|
const remaining = titles.length - visibleCount;
|
|
@@ -3445,7 +3498,7 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3445
3498
|
: relay.polling
|
|
3446
3499
|
? L("settings.a2aRelay.status.polling")
|
|
3447
3500
|
: L("settings.a2aRelay.status.disconnected");
|
|
3448
|
-
const profileUrl = `${relay.relayUrl}/${relay.userId}`;
|
|
3501
|
+
const profileUrl = `${relay.relayUrl}/u/${relay.userId}`;
|
|
3449
3502
|
const userIdLink = `<a href="${escapeHtml(profileUrl)}" target="_blank" rel="noopener">${escapeHtml(relay.userId)}</a>`;
|
|
3450
3503
|
const relayHost = (() => { try { return new URL(relay.relayUrl).host; } catch { return relay.relayUrl; } })();
|
|
3451
3504
|
const publicChecked = relay.acceptPublicTasks === true;
|
|
@@ -3465,20 +3518,22 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3465
3518
|
|
|
3466
3519
|
return `
|
|
3467
3520
|
<div class="settings-page">
|
|
3521
|
+
${renderSettingsGroup("", [
|
|
3522
|
+
renderSettingsInfoRow(L("settings.row.a2aStatus"), statusLabel),
|
|
3523
|
+
renderSettingsInfoRow(L("settings.row.a2aUserId"), userIdLink, { rawValue: true }),
|
|
3524
|
+
renderSettingsInfoRow(L("settings.row.a2aRelay"), relayHost),
|
|
3525
|
+
renderSettingsInfoRow(L("settings.row.a2aApiKey"), relay.apiKeyConfigured ? L("settings.a2aRelay.apiKey.configured") : L("settings.a2aRelay.apiKey.notConfigured")),
|
|
3526
|
+
relay.lastPollAtMs
|
|
3527
|
+
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3528
|
+
: "",
|
|
3529
|
+
].filter(Boolean))}
|
|
3468
3530
|
${(() => {
|
|
3469
3531
|
const stats = relay.taskStats || { received: 0, completed: 0, denied: 0 };
|
|
3470
|
-
return renderSettingsGroup("", [
|
|
3471
|
-
renderSettingsInfoRow(L("settings.row.a2aStatus"), statusLabel),
|
|
3472
|
-
renderSettingsInfoRow(L("settings.row.a2aUserId"), userIdLink, { rawValue: true }),
|
|
3473
|
-
renderSettingsInfoRow(L("settings.row.a2aRelay"), relayHost),
|
|
3474
|
-
renderSettingsInfoRow(L("settings.row.a2aApiKey"), relay.apiKeyConfigured ? L("settings.a2aRelay.apiKey.configured") : L("settings.a2aRelay.apiKey.notConfigured")),
|
|
3475
|
-
relay.lastPollAtMs
|
|
3476
|
-
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3477
|
-
: "",
|
|
3532
|
+
return renderSettingsGroup(L("settings.a2aRelay.taskStats.title"), [
|
|
3478
3533
|
renderSettingsInfoRow(L("settings.row.a2aTaskReceived"), String(stats.received)),
|
|
3479
3534
|
renderSettingsInfoRow(L("settings.row.a2aTaskCompleted"), String(stats.completed)),
|
|
3480
3535
|
renderSettingsInfoRow(L("settings.row.a2aTaskDenied"), String(stats.denied)),
|
|
3481
|
-
]
|
|
3536
|
+
]);
|
|
3482
3537
|
})()}
|
|
3483
3538
|
<section class="settings-group">
|
|
3484
3539
|
<p class="settings-group__title">${escapeHtml(L("settings.a2aRelay.publicTasks.title"))}</p>
|
|
@@ -3506,6 +3561,150 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3506
3561
|
`;
|
|
3507
3562
|
}
|
|
3508
3563
|
|
|
3564
|
+
function renderSettingsA2aSharePage(context) {
|
|
3565
|
+
const share = context.a2aShare;
|
|
3566
|
+
if (!share?.enabled) {
|
|
3567
|
+
return `
|
|
3568
|
+
<div class="settings-page">
|
|
3569
|
+
<p class="settings-page-copy muted">${escapeHtml(L("settings.a2aShare.unavailable"))}</p>
|
|
3570
|
+
</div>
|
|
3571
|
+
`;
|
|
3572
|
+
}
|
|
3573
|
+
const quota = share.quota || { bytes: 0, maxBytes: 0, count: 0, maxCount: 0 };
|
|
3574
|
+
const limits = share.limits || {};
|
|
3575
|
+
const items = Array.isArray(share.items) ? share.items : [];
|
|
3576
|
+
const statusLabel = share.error
|
|
3577
|
+
? L("settings.a2aShare.status.unreachable")
|
|
3578
|
+
: L("settings.status.enabled");
|
|
3579
|
+
const formatBytes = (bytes) => {
|
|
3580
|
+
if (!Number.isFinite(bytes)) return "—";
|
|
3581
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3582
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3583
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
3584
|
+
};
|
|
3585
|
+
const storageValue = share.quota
|
|
3586
|
+
? `${formatBytes(quota.bytes)} / ${formatBytes(quota.maxBytes || limits.maxTotalBytes || 0)}`
|
|
3587
|
+
: "—";
|
|
3588
|
+
const filesValue = share.quota
|
|
3589
|
+
? `${quota.count} / ${quota.maxCount || limits.maxFiles || 0}`
|
|
3590
|
+
: "—";
|
|
3591
|
+
const storageRows = [
|
|
3592
|
+
renderSettingsInfoRow(L("settings.row.a2aShareStorage"), storageValue),
|
|
3593
|
+
renderSettingsInfoRow(L("settings.row.a2aShareFiles"), filesValue),
|
|
3594
|
+
renderSettingsInfoRow(L("settings.row.a2aShareMaxFileSize"), formatBytes(limits.maxFileBytes || 0)),
|
|
3595
|
+
renderSettingsInfoRow(
|
|
3596
|
+
L("settings.row.a2aShareDefaultExpiry"),
|
|
3597
|
+
L("settings.a2aShare.days", { count: limits.defaultExpiresDays || 30 })
|
|
3598
|
+
),
|
|
3599
|
+
renderSettingsInfoRow(
|
|
3600
|
+
L("settings.row.a2aShareUploadRate"),
|
|
3601
|
+
L("settings.a2aShare.ratePerHour", { count: limits.uploadRatePerHour || 10 })
|
|
3602
|
+
),
|
|
3603
|
+
];
|
|
3604
|
+
|
|
3605
|
+
const PAGE_SIZE = 5;
|
|
3606
|
+
const visibleCount = state.a2aShareRecentExpanded || PAGE_SIZE;
|
|
3607
|
+
const visible = items.slice(0, visibleCount);
|
|
3608
|
+
const hasMore = items.length > visibleCount;
|
|
3609
|
+
const filesList = visible.map((item) => {
|
|
3610
|
+
const lock = item.hasPassword
|
|
3611
|
+
? `<span class="settings-compose-badge settings-compose-badge--reply" title="${escapeHtml(L("settings.a2aShare.passwordProtected"))}">🔒</span>`
|
|
3612
|
+
: "";
|
|
3613
|
+
const label = escapeHtml(item.originalName || item.slug);
|
|
3614
|
+
const link = item.url
|
|
3615
|
+
? `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${label}</a>`
|
|
3616
|
+
: label;
|
|
3617
|
+
const sizeText = escapeHtml(formatBytes(item.size || 0));
|
|
3618
|
+
const createdText = item.createdAtMs
|
|
3619
|
+
? escapeHtml(formatRelativeAge(Date.now() - item.createdAtMs))
|
|
3620
|
+
: "";
|
|
3621
|
+
const expiresText = item.expiresAtMs
|
|
3622
|
+
? escapeHtml(formatExpiresIn(item.expiresAtMs - Date.now()))
|
|
3623
|
+
: "";
|
|
3624
|
+
const meta = [sizeText, createdText, expiresText].filter(Boolean).join(" · ");
|
|
3625
|
+
return `
|
|
3626
|
+
<div class="settings-compose-entry settings-icon-entry">
|
|
3627
|
+
<span class="settings-icon-entry__icon settings-icon-entry__icon--file" aria-hidden="true">${renderIcon("file-event")}</span>
|
|
3628
|
+
<span class="settings-icon-entry__body">
|
|
3629
|
+
<span class="settings-icon-entry__title-row">
|
|
3630
|
+
<span class="settings-compose-entry__title">${link}</span>
|
|
3631
|
+
${lock}
|
|
3632
|
+
</span>
|
|
3633
|
+
${meta ? `<span class="settings-compose-entry__meta muted">${meta}</span>` : ""}
|
|
3634
|
+
</span>
|
|
3635
|
+
</div>
|
|
3636
|
+
`;
|
|
3637
|
+
});
|
|
3638
|
+
if (hasMore) {
|
|
3639
|
+
const remaining = items.length - visibleCount;
|
|
3640
|
+
filesList.push(
|
|
3641
|
+
`<button type="button" class="settings-compose-more" data-a2a-share-files-more>${escapeHtml(L("settings.a2aShare.showMore", { count: remaining }))}</button>`
|
|
3642
|
+
);
|
|
3643
|
+
} else if (items.length > PAGE_SIZE) {
|
|
3644
|
+
filesList.push(
|
|
3645
|
+
`<button type="button" class="settings-compose-more" data-a2a-share-files-collapse>${escapeHtml(L("settings.a2aShare.showLess"))}</button>`
|
|
3646
|
+
);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
return `
|
|
3650
|
+
<div class="settings-page">
|
|
3651
|
+
${renderSettingsGroup("", [
|
|
3652
|
+
renderSettingsInfoRow(L("settings.row.a2aShareStatus"), statusLabel),
|
|
3653
|
+
renderSettingsInfoRow(L("settings.row.a2aShareEndpoint"), share.shareHost || share.shareUrl || ""),
|
|
3654
|
+
renderSettingsInfoRow(L("settings.row.a2aShareUserId"), share.userId || ""),
|
|
3655
|
+
])}
|
|
3656
|
+
${renderSettingsGroup(L("settings.a2aShare.storage.title"), storageRows)}
|
|
3657
|
+
${items.length
|
|
3658
|
+
? renderSettingsGroup(L("settings.a2aShare.files.title"), filesList)
|
|
3659
|
+
: renderSettingsGroup(L("settings.a2aShare.files.title"), [
|
|
3660
|
+
`<p class="settings-group__description muted">${escapeHtml(L("settings.a2aShare.files.empty"))}</p>`,
|
|
3661
|
+
])}
|
|
3662
|
+
${share.error ? `<p class="settings-page-copy muted">${escapeHtml(L("settings.a2aShare.error", { reason: share.error }))}</p>` : ""}
|
|
3663
|
+
</div>
|
|
3664
|
+
`;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
function formatRelativeAge(ms) {
|
|
3668
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
3669
|
+
// Intl.RelativeTimeFormat with numeric:"auto" gives us locale-aware phrasing
|
|
3670
|
+
// ("5 minutes ago" / "5分前") without needing dedicated translation keys.
|
|
3671
|
+
const locale = state.locale || DEFAULT_LOCALE;
|
|
3672
|
+
let rtf;
|
|
3673
|
+
try {
|
|
3674
|
+
rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
3675
|
+
} catch {
|
|
3676
|
+
rtf = null;
|
|
3677
|
+
}
|
|
3678
|
+
const sec = Math.floor(ms / 1000);
|
|
3679
|
+
const pick = (value, unit, fallback) => (rtf ? rtf.format(-value, unit) : fallback);
|
|
3680
|
+
if (sec < 60) return pick(sec, "second", `${sec}s ago`);
|
|
3681
|
+
const min = Math.floor(sec / 60);
|
|
3682
|
+
if (min < 60) return pick(min, "minute", `${min}m ago`);
|
|
3683
|
+
const hr = Math.floor(min / 60);
|
|
3684
|
+
if (hr < 24) return pick(hr, "hour", `${hr}h ago`);
|
|
3685
|
+
const day = Math.floor(hr / 24);
|
|
3686
|
+
if (day < 30) return pick(day, "day", `${day}d ago`);
|
|
3687
|
+
const mo = Math.floor(day / 30);
|
|
3688
|
+
if (mo < 12) return pick(mo, "month", `${mo}mo ago`);
|
|
3689
|
+
return pick(Math.floor(mo / 12), "year", `${Math.floor(mo / 12)}y ago`);
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
function formatExpiresIn(ms) {
|
|
3693
|
+
if (!Number.isFinite(ms)) return "";
|
|
3694
|
+
if (ms <= 0) return L("settings.a2aShare.expired");
|
|
3695
|
+
const locale = state.locale || DEFAULT_LOCALE;
|
|
3696
|
+
let rtf;
|
|
3697
|
+
try {
|
|
3698
|
+
rtf = new Intl.RelativeTimeFormat(locale, { numeric: "always" });
|
|
3699
|
+
} catch {
|
|
3700
|
+
rtf = null;
|
|
3701
|
+
}
|
|
3702
|
+
const day = Math.floor(ms / 86400000);
|
|
3703
|
+
if (day >= 1) return rtf ? rtf.format(day, "day") : `in ${day}d`;
|
|
3704
|
+
const hr = Math.max(1, Math.floor(ms / 3600000));
|
|
3705
|
+
return rtf ? rtf.format(hr, "hour") : `in ${hr}h`;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3509
3708
|
function renderSettingsInfoRow(label, value, options = {}) {
|
|
3510
3709
|
const rowClassName = ["settings-info-row", options.rowClassName || ""].filter(Boolean).join(" ");
|
|
3511
3710
|
const valueClassName = ["settings-info-row__value", options.valueClassName || ""].filter(Boolean).join(" ");
|
|
@@ -5393,6 +5592,19 @@ function bindShellInteractions() {
|
|
|
5393
5592
|
});
|
|
5394
5593
|
}
|
|
5395
5594
|
|
|
5595
|
+
for (const btn of document.querySelectorAll("[data-a2a-share-files-more]")) {
|
|
5596
|
+
btn.addEventListener("click", async () => {
|
|
5597
|
+
state.a2aShareRecentExpanded = (state.a2aShareRecentExpanded || 5) + 5;
|
|
5598
|
+
await renderShell();
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
for (const btn of document.querySelectorAll("[data-a2a-share-files-collapse]")) {
|
|
5602
|
+
btn.addEventListener("click", async () => {
|
|
5603
|
+
state.a2aShareRecentExpanded = 0;
|
|
5604
|
+
await renderShell();
|
|
5605
|
+
});
|
|
5606
|
+
}
|
|
5607
|
+
|
|
5396
5608
|
for (const button of document.querySelectorAll("[data-locale-option]")) {
|
|
5397
5609
|
button.addEventListener("click", async () => {
|
|
5398
5610
|
state.pushError = "";
|
package/web/i18n.js
CHANGED
|
@@ -286,6 +286,8 @@ const translations = {
|
|
|
286
286
|
"settings.status.installed": "Installed",
|
|
287
287
|
"settings.status.notInstalled": "Not installed",
|
|
288
288
|
"settings.status.connected": "Connected",
|
|
289
|
+
"settings.status.enabled": "Enabled",
|
|
290
|
+
"settings.status.disabled": "Disabled",
|
|
289
291
|
"settings.status.info": "Info",
|
|
290
292
|
"settings.notifications.title": "Notifications",
|
|
291
293
|
"settings.claudeAway.title": "Claude",
|
|
@@ -361,9 +363,10 @@ const translations = {
|
|
|
361
363
|
"settings.row.languageSource": "Language source",
|
|
362
364
|
"settings.row.defaultLanguage": "Setup default",
|
|
363
365
|
"settings.moltbook.title": "Moltbook",
|
|
364
|
-
"settings.moltbook.subtitle": "Auto-scout
|
|
366
|
+
"settings.moltbook.subtitle": "Auto-scout & notifications",
|
|
365
367
|
"settings.moltbook.copy": "View your daily auto-scout posting status and quota.",
|
|
366
368
|
"settings.moltbook.unavailable": "Moltbook is not configured.",
|
|
369
|
+
"settings.row.moltbookAccount": "Profile",
|
|
367
370
|
"settings.row.moltbookQuota": "Replies today",
|
|
368
371
|
"settings.row.moltbookSeenPosts": "Seen posts",
|
|
369
372
|
"settings.moltbook.batchTitle": "Current batch",
|
|
@@ -407,6 +410,7 @@ const translations = {
|
|
|
407
410
|
"settings.row.a2aTaskReceived": "Tasks received",
|
|
408
411
|
"settings.row.a2aTaskCompleted": "Tasks completed",
|
|
409
412
|
"settings.row.a2aTaskDenied": "Tasks denied",
|
|
413
|
+
"settings.a2aRelay.taskStats.title": "Task history",
|
|
410
414
|
"a2a.task.eyebrow": "A2A Task Request",
|
|
411
415
|
"a2a.task.from": "From",
|
|
412
416
|
"a2a.task.instruction": "Task",
|
|
@@ -426,6 +430,29 @@ const translations = {
|
|
|
426
430
|
"settings.a2aExecutor.ask": "Ask every time",
|
|
427
431
|
"settings.a2aExecutor.unavailable": "Not detected",
|
|
428
432
|
"settings.a2aExecutor.detected": "Detected",
|
|
433
|
+
"settings.a2aShare.title": "A2A Share",
|
|
434
|
+
"settings.a2aShare.subtitle": "HTML hosting",
|
|
435
|
+
"settings.a2aShare.copy": "Host static HTML files on a private URL. Shares the A2A credentials; no separate setup needed.",
|
|
436
|
+
"settings.a2aShare.unavailable": "A2A Share is not configured. Run `viveworker a2a setup` to provision credentials.",
|
|
437
|
+
"settings.a2aShare.storage.title": "Storage",
|
|
438
|
+
"settings.a2aShare.files.title": "Active shares",
|
|
439
|
+
"settings.a2aShare.files.empty": "No active shares yet.",
|
|
440
|
+
"settings.a2aShare.days": ({ count }) => `${count} ${count === 1 ? "day" : "days"}`,
|
|
441
|
+
"settings.a2aShare.ratePerHour": ({ count }) => `${count} / hour`,
|
|
442
|
+
"settings.a2aShare.passwordProtected": "Password protected",
|
|
443
|
+
"settings.a2aShare.showMore": "{count} more…",
|
|
444
|
+
"settings.a2aShare.showLess": "Show less",
|
|
445
|
+
"settings.a2aShare.expired": "Expired",
|
|
446
|
+
"settings.a2aShare.status.unreachable": "Unreachable",
|
|
447
|
+
"settings.a2aShare.error": "Upstream fetch failed: {reason}",
|
|
448
|
+
"settings.row.a2aShareStatus": "Status",
|
|
449
|
+
"settings.row.a2aShareEndpoint": "Endpoint",
|
|
450
|
+
"settings.row.a2aShareUserId": "User ID",
|
|
451
|
+
"settings.row.a2aShareStorage": "Storage used",
|
|
452
|
+
"settings.row.a2aShareFiles": "Active files",
|
|
453
|
+
"settings.row.a2aShareMaxFileSize": "Max file size",
|
|
454
|
+
"settings.row.a2aShareDefaultExpiry": "Default expiry",
|
|
455
|
+
"settings.row.a2aShareUploadRate": "Upload rate limit",
|
|
429
456
|
"a2a.task.resolved": "This task has already been resolved.",
|
|
430
457
|
"a2a.task.statusSubmitted": "Awaiting your decision",
|
|
431
458
|
"a2a.task.statusWorking": "Executing...",
|
|
@@ -942,6 +969,8 @@ const translations = {
|
|
|
942
969
|
"settings.status.installed": "追加済み",
|
|
943
970
|
"settings.status.notInstalled": "未追加",
|
|
944
971
|
"settings.status.connected": "接続済み",
|
|
972
|
+
"settings.status.enabled": "有効",
|
|
973
|
+
"settings.status.disabled": "無効",
|
|
945
974
|
"settings.status.info": "情報",
|
|
946
975
|
"settings.notifications.title": "通知",
|
|
947
976
|
"settings.claudeAway.title": "Claude",
|
|
@@ -1017,9 +1046,10 @@ const translations = {
|
|
|
1017
1046
|
"settings.row.languageSource": "言語ソース",
|
|
1018
1047
|
"settings.row.defaultLanguage": "setup 時の既定値",
|
|
1019
1048
|
"settings.moltbook.title": "Moltbook",
|
|
1020
|
-
"settings.moltbook.subtitle": "
|
|
1049
|
+
"settings.moltbook.subtitle": "自動スカウト・通知連携",
|
|
1021
1050
|
"settings.moltbook.copy": "本日の自動スカウト投稿状況と上限を確認できます。",
|
|
1022
1051
|
"settings.moltbook.unavailable": "Moltbook が設定されていません。",
|
|
1052
|
+
"settings.row.moltbookAccount": "プロフィール",
|
|
1023
1053
|
"settings.row.moltbookQuota": "本日の返信数",
|
|
1024
1054
|
"settings.row.moltbookSeenPosts": "巡回済み投稿数",
|
|
1025
1055
|
"settings.moltbook.batchTitle": "現在のバッチ",
|
|
@@ -1063,6 +1093,7 @@ const translations = {
|
|
|
1063
1093
|
"settings.row.a2aTaskReceived": "受信タスク",
|
|
1064
1094
|
"settings.row.a2aTaskCompleted": "完了タスク",
|
|
1065
1095
|
"settings.row.a2aTaskDenied": "拒否タスク",
|
|
1096
|
+
"settings.a2aRelay.taskStats.title": "タスク履歴",
|
|
1066
1097
|
"a2a.task.eyebrow": "A2A タスクリクエスト",
|
|
1067
1098
|
"a2a.task.from": "送信元",
|
|
1068
1099
|
"a2a.task.instruction": "タスク内容",
|
|
@@ -1082,6 +1113,29 @@ const translations = {
|
|
|
1082
1113
|
"settings.a2aExecutor.ask": "毎回確認する",
|
|
1083
1114
|
"settings.a2aExecutor.unavailable": "未検出",
|
|
1084
1115
|
"settings.a2aExecutor.detected": "検出",
|
|
1116
|
+
"settings.a2aShare.title": "A2A Share",
|
|
1117
|
+
"settings.a2aShare.subtitle": "HTML ホスティング",
|
|
1118
|
+
"settings.a2aShare.copy": "静的 HTML ファイルを非公開 URL でホストします。A2A と認証情報を共有するため追加の設定は不要です。",
|
|
1119
|
+
"settings.a2aShare.unavailable": "A2A Share が設定されていません。`viveworker a2a setup` を実行して認証情報を設定してください。",
|
|
1120
|
+
"settings.a2aShare.storage.title": "ストレージ",
|
|
1121
|
+
"settings.a2aShare.files.title": "公開中のファイル",
|
|
1122
|
+
"settings.a2aShare.files.empty": "公開中のファイルはありません。",
|
|
1123
|
+
"settings.a2aShare.days": ({ count }) => `${count} 日`,
|
|
1124
|
+
"settings.a2aShare.ratePerHour": ({ count }) => `${count} 件 / 1 時間`,
|
|
1125
|
+
"settings.a2aShare.passwordProtected": "パスワード保護",
|
|
1126
|
+
"settings.a2aShare.showMore": "さらに {count} 件…",
|
|
1127
|
+
"settings.a2aShare.showLess": "閉じる",
|
|
1128
|
+
"settings.a2aShare.expired": "期限切れ",
|
|
1129
|
+
"settings.a2aShare.status.unreachable": "到達不可",
|
|
1130
|
+
"settings.a2aShare.error": "上流への問い合わせに失敗しました: {reason}",
|
|
1131
|
+
"settings.row.a2aShareStatus": "ステータス",
|
|
1132
|
+
"settings.row.a2aShareEndpoint": "エンドポイント",
|
|
1133
|
+
"settings.row.a2aShareUserId": "ユーザー ID",
|
|
1134
|
+
"settings.row.a2aShareStorage": "使用容量",
|
|
1135
|
+
"settings.row.a2aShareFiles": "公開中ファイル数",
|
|
1136
|
+
"settings.row.a2aShareMaxFileSize": "1 ファイル上限",
|
|
1137
|
+
"settings.row.a2aShareDefaultExpiry": "既定の公開期限",
|
|
1138
|
+
"settings.row.a2aShareUploadRate": "アップロード上限",
|
|
1085
1139
|
"a2a.task.resolved": "このタスクは既に処理済みです。",
|
|
1086
1140
|
"a2a.task.statusSubmitted": "承認待ち",
|
|
1087
1141
|
"a2a.task.statusWorking": "実行中...",
|