viveworker 0.8.9 → 0.8.10

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
@@ -218,10 +218,19 @@ If you prefer manual setup, add this to an MCP client:
218
218
  Available tools:
219
219
 
220
220
  - `viveworker_status` checks bridge, pairing, Remote connection, A2A, File Share, and Moltbook status
221
+ - `viveworker_stats` reads package adoption and usage stats
222
+ - `viveworker_share_list` lists File Share uploads and optional usage metrics
223
+ - `viveworker_a2a_activity` reads local A2A activity
224
+ - `viveworker_a2a_card` reads the current local A2A agent card
225
+ - `viveworker_moltbook_list` lists Moltbook inbox comments
226
+ - `viveworker_moltbook_show` reads one Moltbook comment
227
+ - `viveworker_moltbook_thread` reads the thread for one Moltbook comment
221
228
  - `viveworker_notify` sends an informational phone notification and timeline entry
222
229
  - `viveworker_ask` asks a question on the paired phone and waits for the answer
223
230
  - `viveworker_request_approval` asks the phone to approve or reject a proposed action
224
231
  - `viveworker_share_file` uploads a workspace file to File Share after phone approval
232
+ - `viveworker_share_replace` replaces the file behind an existing File Share slug after phone approval
233
+ - `viveworker_share_link` mints a short-lived passwordless File Share token URL after phone approval
225
234
  - `viveworker_thread_share` shares context into another Codex / Claude / inbox thread
226
235
  - `viveworker_send_a2a_task` sends a task to a registered A2A target after phone approval
227
236
 
@@ -230,8 +239,9 @@ Available prompts include setup guidance, control-plane usage, risky-action appr
230
239
  Security defaults:
231
240
 
232
241
  - MCP is stdio-only in this release; there is no HTTP MCP server
242
+ - MCP read-only inspection tools use a fixed command allowlist; MCP is not a generic shell or CLI executor
233
243
  - file sharing is limited to the current workspace and refuses `.env`, credential directories, private keys, and secret-looking paths
234
- - File Share and A2A task sending require phone approval
244
+ - File Share uploads/replacements and A2A task sending require phone approval
235
245
  - MCP tool calls are recorded locally with `provider: "mcp"`
236
246
  - prompts, message bodies, file contents, file paths, command text, tokens, public keys, and IP addresses are not sent to central analytics
237
247
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "Mobile control plane for AI agents. Approve, answer, and hand off work for Codex, Claude, MCP-ready tools, and A2A from one phone.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
@@ -37,7 +37,12 @@ Use this flow when the user says "set up viveworker", "pair my phone", "vivework
37
37
  - Use `viveworker_ask` for a short user decision that blocks progress.
38
38
  - Use `viveworker_request_approval` before risky, external, irreversible, payment-related, or user-visible actions.
39
39
  - Use `viveworker_notify` for informational updates that should appear on the phone and timeline.
40
+ - Use `viveworker_stats` for package adoption or usage stats.
41
+ - Use `viveworker_share_list` to inspect File Share uploads or usage metrics.
42
+ - Use `viveworker_a2a_activity` or `viveworker_a2a_card` to inspect A2A activity or local agent-card settings.
43
+ - Use `viveworker_moltbook_list`, `viveworker_moltbook_show`, or `viveworker_moltbook_thread` to inspect Moltbook inbox comments or threads.
40
44
  - Use `viveworker_share_file` when a local deliverable should become a limited File Share URL.
45
+ - Use `viveworker_share_replace` when an existing File Share slug should point at a new file.
41
46
  - Use `viveworker_thread_share` when context should move to another Codex / Claude / inbox thread.
42
47
  - Use `viveworker_send_a2a_task` when the user asks to delegate to a registered A2A target.
43
48
 
@@ -47,7 +52,8 @@ Use this flow when the user says "set up viveworker", "pair my phone", "vivework
47
52
  - Treat timeout, rejection, or missing response as not approved.
48
53
  - Do not send secrets, private keys, `.env` content, credentials, relay tokens, public keys, or unnecessary file contents through MCP.
49
54
  - Do not use MCP as a shell executor. It creates control-plane events; it should not run arbitrary commands.
50
- - Do not upload files through File Share without phone approval.
55
+ - Read-only inspection tools use fixed command allowlists and must not mutate local state.
56
+ - Do not upload or replace files through File Share without phone approval.
51
57
  - Use registered A2A target aliases only. Do not inline API keys.
52
58
 
53
59
  ## Natural language triggers
@@ -127,7 +127,7 @@ class ViveworkerMcpServer {
127
127
  websiteUrl: "https://viveworker.com",
128
128
  },
129
129
  instructions:
130
- "Use viveworker tools when you need to notify, ask, request approval, share a file, hand off context, or delegate an A2A task through the user's paired mobile control plane.",
130
+ "Use viveworker tools when you need to inspect viveworker state, notify, ask, request approval, share a file, hand off context, or delegate an A2A task through the user's paired mobile control plane.",
131
131
  };
132
132
  }
133
133
  }
@@ -138,6 +138,20 @@ async function callTool(params) {
138
138
  switch (name) {
139
139
  case "viveworker_status":
140
140
  return toolOk(await bridgeEvent({ eventType: "status" }, SHORT_TIMEOUT_MS));
141
+ case "viveworker_stats":
142
+ return toolOk(await toolStats(args));
143
+ case "viveworker_share_list":
144
+ return toolOk(await toolShareList(args));
145
+ case "viveworker_a2a_activity":
146
+ return toolOk(await toolA2AActivity(args));
147
+ case "viveworker_a2a_card":
148
+ return toolOk(await toolA2ACard(args));
149
+ case "viveworker_moltbook_list":
150
+ return toolOk(await toolMoltbookList(args));
151
+ case "viveworker_moltbook_show":
152
+ return toolOk(await toolMoltbookShow(args));
153
+ case "viveworker_moltbook_thread":
154
+ return toolOk(await toolMoltbookThread(args));
141
155
  case "viveworker_notify":
142
156
  return toolOk(await toolNotify(args));
143
157
  case "viveworker_ask":
@@ -146,6 +160,8 @@ async function callTool(params) {
146
160
  return toolOk(await toolRequestApproval(args));
147
161
  case "viveworker_share_file":
148
162
  return toolOk(await toolShareFile(args));
163
+ case "viveworker_share_replace":
164
+ return toolOk(await toolShareReplace(args));
149
165
  case "viveworker_share_link":
150
166
  return toolOk(await toolShareLink(args));
151
167
  case "viveworker_thread_share":
@@ -157,6 +173,52 @@ async function callTool(params) {
157
173
  }
158
174
  }
159
175
 
176
+ async function toolStats(args) {
177
+ const pkg = optionalString(args.pkg || args.packageName);
178
+ const cliArgs = ["stats", "--json"];
179
+ if (pkg) {
180
+ assertSafeNpmPackageName(pkg);
181
+ cliArgs.push("--pkg", pkg);
182
+ }
183
+ return runViveworkerCliJson(cliArgs, 60_000);
184
+ }
185
+
186
+ async function toolShareList(args) {
187
+ const cliArgs = ["share", "list", "--json"];
188
+ if (optionalBoolean(args.metrics)) {
189
+ cliArgs.push("--metrics");
190
+ }
191
+ return runViveworkerCliJson(cliArgs, 45_000);
192
+ }
193
+
194
+ async function toolA2AActivity(_args) {
195
+ return runViveworkerCliJson(["a2a", "activity"], 30_000);
196
+ }
197
+
198
+ async function toolA2ACard(_args) {
199
+ const output = await runViveworkerCliText(["a2a", "card"], 30_000);
200
+ return { ok: true, output: output.stdout.trim() };
201
+ }
202
+
203
+ async function toolMoltbookList(args) {
204
+ const cliArgs = ["moltbook", "list"];
205
+ if (optionalBoolean(args.all)) {
206
+ cliArgs.push("--all");
207
+ }
208
+ const output = await runViveworkerCliText(cliArgs, 30_000);
209
+ return { ok: true, output: output.stdout.trim() };
210
+ }
211
+
212
+ async function toolMoltbookShow(args) {
213
+ const commentId = requiredSafeIdentifier(args.commentId, "commentId");
214
+ return runViveworkerCliJson(["moltbook", "show", commentId], 30_000);
215
+ }
216
+
217
+ async function toolMoltbookThread(args) {
218
+ const commentId = requiredSafeIdentifier(args.commentId, "commentId");
219
+ return runViveworkerCliJson(["moltbook", "thread", commentId], 45_000);
220
+ }
221
+
160
222
  async function toolNotify(args) {
161
223
  const title = requiredString(args.title, "title");
162
224
  const message = requiredString(args.message, "message");
@@ -245,11 +307,46 @@ async function toolShareFile(args) {
245
307
  return { approved: true, share: upload, tokenizedLink: link };
246
308
  }
247
309
 
248
- async function toolShareLink(args) {
249
- const slug = requiredString(args.slug, "slug");
250
- if (!/^[A-Za-z0-9]+$/.test(slug)) {
251
- throw rpcInvalidParams("slug must contain only letters and numbers");
310
+ async function toolShareReplace(args) {
311
+ const slug = requiredShareSlug(args.slug);
312
+ const requestedPath = requiredString(args.path, "path");
313
+ const workspaceRoot = optionalString(args.workspaceRoot) || process.env.VIVEWORKER_MCP_WORKSPACE_ROOT || process.cwd();
314
+ const file = await validateSharePath(requestedPath, workspaceRoot, "share_replace");
315
+ const stat = await fs.stat(file.realPath);
316
+ const expiresDays = optionalString(args.expiresDays ?? args["expires-days"]);
317
+ const noOptimize = optionalBoolean(args.noOptimize ?? args["no-optimize"]);
318
+ const approval = await bridgeEvent({
319
+ eventType: "approval_request",
320
+ title: "Replace viveworker File Share file",
321
+ message: [
322
+ "An MCP client wants to replace the file behind an existing viveworker File Share URL.",
323
+ "",
324
+ `Share slug: ${slug}`,
325
+ `New file: ${file.displayPath}`,
326
+ `Size: ${stat.size} bytes`,
327
+ expiresDays ? `Expires days: ${expiresDays}` : "Expires days: keep existing/default",
328
+ noOptimize ? "HTML optimization: disabled for this replace" : "",
329
+ "",
330
+ "This changes what existing recipients see at the same share link.",
331
+ "Approve only if this replacement file is safe to send outside this Mac.",
332
+ ].filter(Boolean).join("\n"),
333
+ approvalKind: "file_share_replace",
334
+ fileRefs: [file.displayPath],
335
+ timeoutMs: clampTimeout(args.timeoutMs),
336
+ }, clampTimeout(args.timeoutMs));
337
+ if (!approval.approved) {
338
+ return { approved: false, decision: approval.decision || "rejected" };
252
339
  }
340
+
341
+ const replaceArgs = ["share", "replace", slug, file.realPath, "--json"];
342
+ if (expiresDays) replaceArgs.push("--expires-days", expiresDays);
343
+ if (noOptimize) replaceArgs.push("--no-optimize");
344
+ const replace = await runViveworkerCliJson(replaceArgs, 90_000);
345
+ return { approved: true, share: replace };
346
+ }
347
+
348
+ async function toolShareLink(args) {
349
+ const slug = requiredShareSlug(args.slug);
253
350
  const password = requiredString(args.password, "password");
254
351
  if (password.length > 256) {
255
352
  throw rpcInvalidParams("password is too long (max 256 characters)");
@@ -424,7 +521,7 @@ function httpJson(endpoint, options) {
424
521
  });
425
522
  }
426
523
 
427
- async function validateSharePath(filePath, workspaceRoot) {
524
+ async function validateSharePath(filePath, workspaceRoot, toolName = "share_file") {
428
525
  const root = path.resolve(String(workspaceRoot || ""));
429
526
  const candidate = path.resolve(String(filePath || ""));
430
527
  const [rootReal, fileReal] = await Promise.all([
@@ -433,16 +530,16 @@ async function validateSharePath(filePath, workspaceRoot) {
433
530
  ]);
434
531
  const rel = path.relative(rootReal, fileReal);
435
532
  if (rel.startsWith("..") || path.isAbsolute(rel)) {
436
- throw new Error("share_file is limited to the current workspace root");
533
+ throw new Error(`${toolName} is limited to the current workspace root`);
437
534
  }
438
535
  const stat = await fs.stat(fileReal);
439
536
  if (!stat.isFile()) {
440
- throw new Error("share_file path must be a regular file");
537
+ throw new Error(`${toolName} path must be a regular file`);
441
538
  }
442
539
  assertNotSensitivePath(rel || path.basename(fileReal));
443
540
  const ext = path.extname(fileReal).toLowerCase();
444
541
  if (!SHARE_FILE_EXTENSIONS.has(ext)) {
445
- throw new Error(`share_file accepts only ${Array.from(SHARE_FILE_EXTENSIONS).join(" / ")} files. Got: ${ext || "(no extension)"}`);
542
+ throw new Error(`${toolName} accepts only ${Array.from(SHARE_FILE_EXTENSIONS).join(" / ")} files. Got: ${ext || "(no extension)"}`);
446
543
  }
447
544
  return {
448
545
  realPath: fileReal,
@@ -476,7 +573,16 @@ function assertNotSensitivePath(relPath) {
476
573
  }
477
574
  }
478
575
 
479
- function runViveworkerCliJson(args, timeoutMs) {
576
+ async function runViveworkerCliJson(args, timeoutMs) {
577
+ const { stdout } = await runViveworkerCliText(args, timeoutMs);
578
+ try {
579
+ return JSON.parse(stdout);
580
+ } catch {
581
+ throw new Error(`viveworker CLI returned non-JSON: ${stdout.slice(0, 200)}`);
582
+ }
583
+ }
584
+
585
+ function runViveworkerCliText(args, timeoutMs) {
480
586
  return new Promise((resolve, reject) => {
481
587
  const child = spawn(process.execPath, [viveworkerCli, ...args], {
482
588
  cwd: packageRoot,
@@ -502,11 +608,7 @@ function runViveworkerCliJson(args, timeoutMs) {
502
608
  reject(new Error((stderr || stdout || `viveworker CLI failed with code ${code}`).trim()));
503
609
  return;
504
610
  }
505
- try {
506
- resolve(JSON.parse(stdout));
507
- } catch {
508
- reject(new Error(`viveworker CLI returned non-JSON: ${stdout.slice(0, 200)}`));
509
- }
611
+ resolve({ stdout, stderr });
510
612
  });
511
613
  });
512
614
  }
@@ -657,6 +759,29 @@ function slugFromShareUrl(value) {
657
759
  }
658
760
  }
659
761
 
762
+ function assertSafeNpmPackageName(value) {
763
+ const text = optionalString(value);
764
+ if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(text)) {
765
+ throw rpcInvalidParams("pkg must be a valid npm package name");
766
+ }
767
+ }
768
+
769
+ function requiredSafeIdentifier(value, name) {
770
+ const text = requiredString(value, name);
771
+ if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(text)) {
772
+ throw rpcInvalidParams(`${name} contains unsupported characters`);
773
+ }
774
+ return text;
775
+ }
776
+
777
+ function requiredShareSlug(value) {
778
+ const slug = requiredString(value, "slug");
779
+ if (!/^[A-Za-z0-9]+$/.test(slug)) {
780
+ throw rpcInvalidParams("slug must contain only letters and numbers");
781
+ }
782
+ return slug;
783
+ }
784
+
660
785
  async function createShareTokenLink(slug, password, ttlHours) {
661
786
  const linkArgs = ["share", "link", slug, "--password", password, "--json"];
662
787
  if (ttlHours !== undefined) {
@@ -808,6 +933,87 @@ const TOOLS = [
808
933
  description: "Return bridge, pairing, remote connection, A2A, and File Share status.",
809
934
  inputSchema: { type: "object", additionalProperties: false },
810
935
  },
936
+ {
937
+ name: "viveworker_stats",
938
+ title: "Read viveworker stats",
939
+ description: "Read viveworker package adoption and usage stats. Read-only.",
940
+ inputSchema: {
941
+ type: "object",
942
+ additionalProperties: false,
943
+ properties: {
944
+ pkg: { type: "string" },
945
+ },
946
+ },
947
+ annotations: { readOnlyHint: true },
948
+ },
949
+ {
950
+ name: "viveworker_share_list",
951
+ title: "List File Share uploads",
952
+ description: "List File Share uploads and optionally include usage metrics. Read-only.",
953
+ inputSchema: {
954
+ type: "object",
955
+ additionalProperties: false,
956
+ properties: {
957
+ metrics: { type: "boolean" },
958
+ },
959
+ },
960
+ annotations: { readOnlyHint: true },
961
+ },
962
+ {
963
+ name: "viveworker_a2a_activity",
964
+ title: "Read A2A activity",
965
+ description: "Read local A2A activity from the default viveworker state. Read-only.",
966
+ inputSchema: { type: "object", additionalProperties: false },
967
+ annotations: { readOnlyHint: true },
968
+ },
969
+ {
970
+ name: "viveworker_a2a_card",
971
+ title: "Read A2A card",
972
+ description: "Read the current local A2A agent card settings. Read-only.",
973
+ inputSchema: { type: "object", additionalProperties: false },
974
+ annotations: { readOnlyHint: true },
975
+ },
976
+ {
977
+ name: "viveworker_moltbook_list",
978
+ title: "List Moltbook inbox",
979
+ description: "List Moltbook inbox comments. Read-only.",
980
+ inputSchema: {
981
+ type: "object",
982
+ additionalProperties: false,
983
+ properties: {
984
+ all: { type: "boolean" },
985
+ },
986
+ },
987
+ annotations: { readOnlyHint: true },
988
+ },
989
+ {
990
+ name: "viveworker_moltbook_show",
991
+ title: "Read Moltbook comment",
992
+ description: "Show one Moltbook comment by commentId. Read-only.",
993
+ inputSchema: {
994
+ type: "object",
995
+ additionalProperties: false,
996
+ properties: {
997
+ commentId: { type: "string" },
998
+ },
999
+ required: ["commentId"],
1000
+ },
1001
+ annotations: { readOnlyHint: true },
1002
+ },
1003
+ {
1004
+ name: "viveworker_moltbook_thread",
1005
+ title: "Read Moltbook thread",
1006
+ description: "Show the Moltbook thread for one commentId. Read-only.",
1007
+ inputSchema: {
1008
+ type: "object",
1009
+ additionalProperties: false,
1010
+ properties: {
1011
+ commentId: { type: "string" },
1012
+ },
1013
+ required: ["commentId"],
1014
+ },
1015
+ annotations: { readOnlyHint: true },
1016
+ },
811
1017
  {
812
1018
  name: "viveworker_notify",
813
1019
  title: "Notify phone",
@@ -893,6 +1099,24 @@ const TOOLS = [
893
1099
  required: ["path"],
894
1100
  },
895
1101
  },
1102
+ {
1103
+ name: "viveworker_share_replace",
1104
+ title: "Replace File Share file",
1105
+ description: "After phone approval, replace the file behind an existing viveworker File Share slug.",
1106
+ inputSchema: {
1107
+ type: "object",
1108
+ additionalProperties: false,
1109
+ properties: {
1110
+ slug: { type: "string" },
1111
+ path: { type: "string" },
1112
+ workspaceRoot: { type: "string" },
1113
+ expiresDays: { type: "string" },
1114
+ noOptimize: { type: "boolean" },
1115
+ timeoutMs: { type: "number" },
1116
+ },
1117
+ required: ["slug", "path"],
1118
+ },
1119
+ },
896
1120
  {
897
1121
  name: "viveworker_share_link",
898
1122
  title: "Create File Share token URL",
@@ -977,7 +1201,7 @@ const PROMPTS = [
977
1201
  title: "Share deliverable",
978
1202
  description: "Package a local deliverable through viveworker File Share with phone approval.",
979
1203
  },
980
- text: "When the user asks to share a report, prototype, screenshot, CSV, or standalone HTML deliverable, use viveworker_share_file. Only share files inside the workspace and never share secrets or credentials. If the user wants a password-protected share but the recipient should not know the password, set password plus tokenize=true, or use viveworker_share_link for an existing password-protected slug to mint a short-lived ?t= URL.",
1204
+ text: "When the user asks to share a report, prototype, screenshot, CSV, or standalone HTML deliverable, use viveworker_share_file. When the user asks to replace the file behind an existing File Share slug, use viveworker_share_replace. Only share files inside the workspace and never share secrets or credentials. If the user wants a password-protected share but the recipient should not know the password, set password plus tokenize=true, or use viveworker_share_link for an existing password-protected slug to mint a short-lived ?t= URL.",
981
1205
  },
982
1206
  {
983
1207
  definition: {
@@ -1803,11 +1803,6 @@ function unwrapShellCommand(commandText) {
1803
1803
  .trim();
1804
1804
  }
1805
1805
 
1806
- function extractCommandLineFromFunctionOutput(outputText) {
1807
- const match = String(outputText || "").match(/^Command:\s+(.+)$/mu);
1808
- return unwrapShellCommand(match?.[1] || "");
1809
- }
1810
-
1811
1806
  function parseToolArgumentsJson(value) {
1812
1807
  if (isPlainObject(value)) {
1813
1808
  return value;
@@ -1864,16 +1859,21 @@ function redactTimelineCommandText(commandText) {
1864
1859
  .replace(/\b([A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[A-Z0-9_]*=)(["']?)[^\s"']+\2/giu, "$1[redacted]");
1865
1860
  }
1866
1861
 
1862
+ function escapeMarkdownCodeFenceBody(text) {
1863
+ return cleanText(text || "").replace(/```/gu, "\\`\\`\\`");
1864
+ }
1865
+
1867
1866
  function timelineCommandMessage(locale, { commandText = "", toolName = "", fileRefs = [] } = {}) {
1867
+ const safeCommandText = escapeMarkdownCodeFenceBody(redactTimelineCommandText(commandText));
1868
1868
  const commandBlock = commandText
1869
- ? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${redactTimelineCommandText(commandText)}\n\`\`\``
1869
+ ? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${safeCommandText}\n\`\`\``
1870
1870
  : "";
1871
1871
  const toolBlock = !commandBlock && toolName
1872
- ? `${t(locale, "server.message.toolLabel")}\n\`\`\`text\n${cleanText(toolName)}\n\`\`\``
1872
+ ? `${t(locale, "server.message.toolLabel")}\n\`\`\`text\n${escapeMarkdownCodeFenceBody(toolName)}\n\`\`\``
1873
1873
  : "";
1874
1874
  const files = normalizeTimelineFileRefs(fileRefs);
1875
1875
  const filesBlock = files.length
1876
- ? `${t(locale, "server.message.filesLabel")}\n\`\`\`text\n${files.join("\n")}\n\`\`\``
1876
+ ? `${t(locale, "server.message.filesLabel")}\n\`\`\`text\n${files.map(escapeMarkdownCodeFenceBody).join("\n")}\n\`\`\``
1877
1877
  : "";
1878
1878
  return [commandBlock || toolBlock, filesBlock].filter(Boolean).join("\n\n");
1879
1879
  }
@@ -2003,7 +2003,7 @@ function sedCommandPathArgs(tokens) {
2003
2003
  function autoPilotApprovalMessage(locale, commandText) {
2004
2004
  const prefix = t(locale, "server.message.autoPilotTrustedReadApproved");
2005
2005
  const commandBlock = cleanText(commandText || "")
2006
- ? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${cleanText(commandText || "")}\n\`\`\``
2006
+ ? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${escapeMarkdownCodeFenceBody(commandText)}\n\`\`\``
2007
2007
  : "";
2008
2008
  return [prefix, commandBlock].filter(Boolean).join("\n\n");
2009
2009
  }
@@ -7080,11 +7080,6 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
7080
7080
  });
7081
7081
  return [];
7082
7082
  }
7083
- const commandText = extractCommandLineFromFunctionOutput(payload.output ?? "");
7084
- if (!commandText) {
7085
- return [];
7086
- }
7087
- const classified = classifyTimelineCommand(commandText);
7088
7083
  upsertTimelineActivity({
7089
7084
  config,
7090
7085
  runtime,
@@ -7096,19 +7091,7 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
7096
7091
  source: "codex-tool-output",
7097
7092
  atMs: createdAtMs,
7098
7093
  });
7099
- return [
7100
- buildToolTimelineEntry({
7101
- provider: "codex",
7102
- threadId,
7103
- threadLabel,
7104
- callId,
7105
- createdAtMs,
7106
- fileEventType: classified.fileEventType,
7107
- commandText: classified.commandText || commandText,
7108
- fileRefs: classified.fileRefs,
7109
- cwd: fileState.cwd || "",
7110
- }),
7111
- ].filter(Boolean);
7094
+ return [];
7112
7095
  }
7113
7096
 
7114
7097
  if (payloadType === "custom_tool_call_output") {
@@ -11033,7 +11016,7 @@ function formatCommandApprovalMessage(params, locale = config?.defaultLocale ||
11033
11016
  parts.push(t(locale, "server.message.commandApprovalNeeded"));
11034
11017
  }
11035
11018
  if (command) {
11036
- parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${command}\n\`\`\``);
11019
+ parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${escapeMarkdownCodeFenceBody(command)}\n\`\`\``);
11037
11020
  }
11038
11021
  return parts.join("\n\n") || t(locale, "server.message.commandApprovalNeeded");
11039
11022
  }
@@ -148,7 +148,7 @@ async function handlePermissionRequest() {
148
148
  } else if (toolName === "Bash") {
149
149
  approvalKind = "command";
150
150
  const cmd = String(toolInput.command || "");
151
- messageText = `Command approval needed.\n\`\`\`\n${cmd.slice(0, 500)}\n\`\`\``;
151
+ messageText = `Command approval needed.\n\`\`\`\n${escapeMarkdownCodeFenceBody(cmd.slice(0, 500))}\n\`\`\``;
152
152
  } else if (toolName === "ExitPlanMode") {
153
153
  approvalKind = "plan";
154
154
  messageText = "Plan approval needed.";
@@ -708,6 +708,10 @@ function sanitizeForPath(s) {
708
708
  return s.replace(/[^a-zA-Z0-9_-]/gu, "_").slice(0, 64);
709
709
  }
710
710
 
711
+ function escapeMarkdownCodeFenceBody(text) {
712
+ return String(text || "").replace(/```/gu, "\\`\\`\\`");
713
+ }
714
+
711
715
  function encodeFilePathForSnapshot(filePath) {
712
716
  return filePath.replace(/[/\\]/gu, "_") + ".snap";
713
717
  }
@@ -34,9 +34,14 @@ Keep onboarding calm and step-by-step. Do not make the user debug config files u
34
34
  ## Tool Selection
35
35
 
36
36
  - Use `viveworker_notify` for informational milestones that should appear on the phone or timeline, such as "build finished", "review ready", or "agent is blocked".
37
+ - Use `viveworker_stats` when the user asks for viveworker package adoption or usage stats.
38
+ - Use `viveworker_share_list` when the user asks what is currently in File Share or asks for share usage metrics.
39
+ - Use `viveworker_a2a_activity` or `viveworker_a2a_card` when troubleshooting A2A activity or local agent-card settings.
40
+ - Use `viveworker_moltbook_list`, `viveworker_moltbook_show`, or `viveworker_moltbook_thread` when inspecting Moltbook inbox comments or threads.
37
41
  - Use `viveworker_ask` when the next step depends on a human preference, missing requirement, or choice that should not be guessed.
38
42
  - Use `viveworker_request_approval` before actions that are externally visible, hard to undo, risky, sensitive, delegated, paid, or likely to surprise the user.
39
43
  - Use `viveworker_share_file` when the user wants a workspace deliverable shared as a limited File Share URL. Good fits include `.html`, `.htm`, `.pdf`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, and `.csv`.
44
+ - Use `viveworker_share_replace` when the user wants to replace or update the file behind an existing File Share slug.
40
45
  - Use `viveworker_thread_share` when context should move to another Codex session, Claude session, or viveworker inbox while preserving a human-visible handoff.
41
46
  - Use `viveworker_send_a2a_task` only for registered A2A target aliases and bounded tasks with clear acceptance criteria.
42
47
 
@@ -47,10 +52,12 @@ Keep onboarding calm and step-by-step. Do not make the user debug config files u
47
52
  - Include relevant file refs, but never include secrets, tokens, private keys, or unnecessary file contents.
48
53
  - Treat timeout, rejection, or transport failure as "not approved". Do not continue as if approval was granted.
49
54
  - After the tool returns, summarize the decision or returned artifact to the user.
55
+ - Read-only inspection tools use fixed command allowlists. Do not try to route arbitrary shell commands or side-effectful viveworker CLI actions through MCP.
50
56
 
51
57
  ## File Share Rules
52
58
 
53
59
  - Share only files inside the workspace root.
60
+ - Replacing an existing share changes what existing recipients see at that URL; require phone approval and make that consequence clear.
54
61
  - Do not share `.env`, credential files, private keys, `.ssh`, `.aws`, `.gnupg`, or secret-looking paths.
55
62
  - Prefer sharing final deliverables, not internal source files, unless the user explicitly asks.
56
63
  - For unsupported file types, explain the accepted types and suggest exporting to HTML, PDF, image, or CSV first.
@@ -23,10 +23,18 @@ Then restart the Claude Code session. If the tools are not available, ask the us
23
23
  ## Tools
24
24
 
25
25
  - `viveworker_status` checks bridge, pairing, Remote connection, A2A, File Share, and Moltbook state.
26
+ - `viveworker_stats` reads package adoption and usage stats.
27
+ - `viveworker_share_list` lists File Share uploads and optional usage metrics.
28
+ - `viveworker_a2a_activity` reads local A2A activity.
29
+ - `viveworker_a2a_card` reads the current local A2A agent card.
30
+ - `viveworker_moltbook_list` lists Moltbook inbox comments.
31
+ - `viveworker_moltbook_show` reads one Moltbook comment.
32
+ - `viveworker_moltbook_thread` reads the thread for one Moltbook comment.
26
33
  - `viveworker_notify` sends an informational phone notification and records a timeline entry.
27
34
  - `viveworker_ask` asks the paired phone a question and waits for the answer.
28
35
  - `viveworker_request_approval` asks the phone to approve or reject a proposed action.
29
36
  - `viveworker_share_file` uploads a workspace file to File Share after phone approval. For password-protected handoff, pass `tokenize: true` to return a short-lived passwordless `?t=` URL.
37
+ - `viveworker_share_replace` replaces the file behind an existing File Share slug after phone approval.
30
38
  - `viveworker_share_link` mints a short-lived passwordless `?t=` URL for an existing password-protected File Share slug after phone approval.
31
39
  - `viveworker_thread_share` shares context into another Codex / Claude / inbox thread.
32
40
  - `viveworker_send_a2a_task` sends a task to a registered A2A target after phone approval.
@@ -36,9 +44,11 @@ Then restart the Claude Code session. If the tools are not available, ask the us
36
44
  - If the user says "ask me on my phone", "スマホに聞いて", or a short decision blocks progress, use `viveworker_ask`.
37
45
  - If the user asks you to proceed with a risky, external, irreversible, payment-related, or user-visible action, use `viveworker_request_approval`.
38
46
  - If the user asks for a report, prototype, screenshot, PDF, CSV, or standalone HTML to become a shareable link, use `viveworker_share_file`.
47
+ - If the user asks to replace or update the file at an existing File Share link, use `viveworker_share_replace`.
39
48
  - If the user asks for a password-protected share but wants the recipient to open it without knowing the password, use `viveworker_share_file` with `password` and `tokenize: true`, or use `viveworker_share_link` for an existing slug.
40
49
  - If the user says "share this with Codex/Claude", "Aの内容をBに共有して", or wants context handed to another session, use `viveworker_thread_share`.
41
50
  - If the user wants another registered agent to do work, use `viveworker_send_a2a_task`.
51
+ - For troubleshooting or inspection, prefer the read-only MCP tools above before asking the user to run CLI commands.
42
52
 
43
53
  ## Prompting rules
44
54
 
@@ -64,6 +74,7 @@ Claude may not automatically notice newly shared messages from another session.
64
74
 
65
75
  - MCP is a control-plane surface, not a shell executor.
66
76
  - Do not run shell strings through MCP.
67
- - File Share and A2A task sending require phone approval.
77
+ - Read-only inspection tools use fixed command allowlists and should not be used to mutate local state.
78
+ - File Share uploads/replacements and A2A task sending require phone approval.
68
79
  - Use registered A2A aliases only; do not ask the user to paste API keys into prompts.
69
80
  - Prefer `viveworker_status` before troubleshooting a missing notification, stale pairing, or remote connectivity issue.