viveworker 0.5.2 → 0.5.5
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 +585 -72
- package/web/app.css +175 -1
- package/web/app.js +408 -17
- package/web/i18n.js +88 -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.5",
|
|
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() {
|