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 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.2",
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",
@@ -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
  // ---------------------------------------------------------------------------
@@ -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 res = await fetch(`${API_BASE}${pathname}`, {
43
- ...init,
44
- headers: {
45
- "content-type": "application/json",
46
- authorization: `Bearer ${apiKey}`,
47
- ...(init.headers || {}),
48
- },
49
- });
50
- const text = await res.text().catch(() => "");
51
- if (!res.ok) {
52
- throw new Error(`moltbook ${res.status} ${pathname}: ${text}`);
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
- return JSON.parse(text);
56
- } catch {
57
- return text;
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
- state.composedToday = (state.composedToday || 0) + 1;
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
- const res = await fetch(`${VIVEWORKER_BASE}/api/providers/moltbook/events`, {
75
- method: "POST",
76
- headers: {
77
- "content-type": "application/json",
78
- "x-viveworker-hook-secret": HOOK_SECRET,
79
- },
80
- body: JSON.stringify(item),
81
- });
82
- if (!res.ok) {
83
- throw new Error(`bridge ${res.status}: ${await res.text().catch(() => "")}`);
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() {