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 +11 -1
- package/package.json +1 -1
- package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +7 -1
- package/scripts/mcp-server.mjs +240 -16
- package/scripts/viveworker-bridge.mjs +11 -28
- package/scripts/viveworker-claude-hook.mjs +5 -1
- package/skills/viveworker-control-plane/SKILL.md +7 -0
- package/templates/CLAUDE.viveworker.md +12 -1
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.
|
|
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
|
-
-
|
|
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
|
package/scripts/mcp-server.mjs
CHANGED
|
@@ -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
|
|
249
|
-
const slug =
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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${
|
|
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${
|
|
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${
|
|
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
|
-
-
|
|
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.
|