viveworker 0.8.0 → 0.8.2
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/.agents/plugins/marketplace.json +20 -0
- package/README.md +141 -0
- package/package.json +7 -1
- package/plugins/viveworker-control-plane/.codex-plugin/plugin.json +49 -0
- package/plugins/viveworker-control-plane/.mcp.json +11 -0
- package/plugins/viveworker-control-plane/DISTRIBUTION.md +96 -0
- package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.png +0 -0
- package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.svg +39 -0
- package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +83 -0
- package/scripts/a2a-executor.mjs +261 -7
- package/scripts/a2a-handler.mjs +88 -0
- package/scripts/a2a-relay-client.mjs +6 -0
- package/scripts/lib/markdown-render.mjs +128 -1
- package/scripts/mcp-server.mjs +891 -0
- package/scripts/stats-cli.mjs +683 -0
- package/scripts/viveworker-bridge.mjs +1504 -128
- package/scripts/viveworker.mjs +262 -1
- package/skills/viveworker-control-plane/SKILL.md +104 -0
- package/skills/viveworker-control-plane/agents/openai.yaml +12 -0
- package/templates/CLAUDE.viveworker.md +67 -0
- package/web/app.css +162 -0
- package/web/app.js +1164 -101
- package/web/build-id.js +1 -0
- package/web/i18n.js +123 -15
- package/web/icons/apple-touch-icon.png +0 -0
- package/web/icons/viveworker-icon-192.png +0 -0
- package/web/icons/viveworker-icon-512.png +0 -0
- package/web/icons/viveworker-v-pulse.svg +16 -1
- package/web/index.html +2 -2
- package/web/remote-pairing/api-router.js +84 -0
- package/web/remote-pairing/transport.js +67 -2
- package/web/sw.js +16 -6
- package/web/icons/viveworker-beacon-v.svg +0 -19
- package/web/icons/viveworker-icon-1024.png +0 -0
- package/web/icons/viveworker-v-check.svg +0 -19
package/scripts/viveworker.mjs
CHANGED
|
@@ -75,6 +75,17 @@ if (rawArgs[0] === "stats") {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
if (rawArgs[0] === "mcp") {
|
|
79
|
+
const { runMcpCli } = await import("./mcp-server.mjs");
|
|
80
|
+
try {
|
|
81
|
+
await runMcpCli(rawArgs.slice(1));
|
|
82
|
+
process.exit(0);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(error.message || String(error));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
const cli = parseArgs(rawArgs);
|
|
79
90
|
|
|
80
91
|
try {
|
|
@@ -388,7 +399,7 @@ async function runPair(cliOptions) {
|
|
|
388
399
|
async function runEnable(cliOptions) {
|
|
389
400
|
const target = String(cliOptions.enableTarget || "").trim().toLowerCase();
|
|
390
401
|
if (!target) {
|
|
391
|
-
throw new Error("Usage: viveworker enable <claude|a2a|moltbook|scout> [...]");
|
|
402
|
+
throw new Error("Usage: viveworker enable <claude|a2a|moltbook|scout|mcp> [...]");
|
|
392
403
|
}
|
|
393
404
|
|
|
394
405
|
switch (target) {
|
|
@@ -406,11 +417,248 @@ async function runEnable(cliOptions) {
|
|
|
406
417
|
case "scout":
|
|
407
418
|
await runEnableScout(cliOptions);
|
|
408
419
|
return;
|
|
420
|
+
case "mcp":
|
|
421
|
+
await runEnableMcp(cliOptions);
|
|
422
|
+
return;
|
|
409
423
|
default:
|
|
410
424
|
throw new Error(`Unknown feature: ${target}`);
|
|
411
425
|
}
|
|
412
426
|
}
|
|
413
427
|
|
|
428
|
+
async function runEnableMcp(cliOptions) {
|
|
429
|
+
const target = normalizeMcpInstallTarget(cliOptions.mcpTarget || "all");
|
|
430
|
+
const allTargets = target === "all" ? ["claude", "cursor", "codex"] : [target];
|
|
431
|
+
const entries = [];
|
|
432
|
+
const config = mcpServerConfigSnippet();
|
|
433
|
+
|
|
434
|
+
for (const item of allTargets) {
|
|
435
|
+
const entry = await prepareMcpConfigChange(item, config, { explicit: target !== "all" });
|
|
436
|
+
if (entry) entries.push(entry);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (entries.length === 0) {
|
|
440
|
+
console.log("");
|
|
441
|
+
console.log("No supported MCP client config was found.");
|
|
442
|
+
console.log("Run `npx viveworker mcp config` to print a manual config snippet.");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
console.log("");
|
|
447
|
+
console.log("viveworker MCP setup");
|
|
448
|
+
console.log("");
|
|
449
|
+
console.log("The following MCP client config changes will be applied:");
|
|
450
|
+
for (const entry of entries) {
|
|
451
|
+
console.log(`- ${entry.label}: ${entry.status} ${entry.path}`);
|
|
452
|
+
}
|
|
453
|
+
console.log("");
|
|
454
|
+
console.log("MCP server entry:");
|
|
455
|
+
console.log(JSON.stringify({ mcpServers: { viveworker: config } }, null, 2));
|
|
456
|
+
|
|
457
|
+
if (cliOptions.mcpDryRun) {
|
|
458
|
+
console.log("");
|
|
459
|
+
console.log("Dry run only. No files were changed.");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const changedEntries = entries.filter((entry) => entry.changed);
|
|
464
|
+
if (changedEntries.length === 0) {
|
|
465
|
+
console.log("");
|
|
466
|
+
console.log("MCP config is already up to date.");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!cliOptions.yes) {
|
|
471
|
+
const confirmed = await confirmCli("Write these MCP config changes? [y/N] ");
|
|
472
|
+
if (!confirmed) {
|
|
473
|
+
console.log("Cancelled.");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
for (const entry of changedEntries) {
|
|
479
|
+
await fs.mkdir(path.dirname(entry.path), { recursive: true });
|
|
480
|
+
await fs.writeFile(entry.path, entry.nextText, entry.mode || "utf8");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log("");
|
|
484
|
+
console.log("MCP config updated.");
|
|
485
|
+
console.log("Restart the target app so it reloads the MCP server list.");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function normalizeMcpInstallTarget(value) {
|
|
489
|
+
const target = String(value || "all").trim().toLowerCase();
|
|
490
|
+
if (["claude", "cursor", "codex", "all"].includes(target)) {
|
|
491
|
+
return target;
|
|
492
|
+
}
|
|
493
|
+
throw new Error("Use --target claude, --target cursor, --target codex, or --target all.");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function mcpServerConfigSnippet() {
|
|
497
|
+
return {
|
|
498
|
+
command: "npx",
|
|
499
|
+
args: ["viveworker", "mcp"],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function prepareMcpConfigChange(target, serverConfig, { explicit = false } = {}) {
|
|
504
|
+
const descriptor = mcpConfigDescriptor(target);
|
|
505
|
+
if (!descriptor) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
if (!explicit && !(await fileExists(descriptor.parentDir))) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const currentText = await readOptionalText(descriptor.path);
|
|
512
|
+
const result = descriptor.kind === "toml"
|
|
513
|
+
? upsertCodexMcpConfig(currentText, serverConfig)
|
|
514
|
+
: upsertJsonMcpConfig(currentText, serverConfig);
|
|
515
|
+
return {
|
|
516
|
+
target,
|
|
517
|
+
label: descriptor.label,
|
|
518
|
+
path: descriptor.path,
|
|
519
|
+
status: result.changed
|
|
520
|
+
? (currentText.trim() ? "update" : "create")
|
|
521
|
+
: "unchanged",
|
|
522
|
+
changed: result.changed,
|
|
523
|
+
nextText: result.text,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function mcpConfigDescriptor(target) {
|
|
528
|
+
if (target === "claude") {
|
|
529
|
+
const override = process.env.VIVEWORKER_MCP_CLAUDE_CONFIG_FILE || "";
|
|
530
|
+
const defaultPath = path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
531
|
+
const configPath = resolvePath(override || defaultPath);
|
|
532
|
+
return {
|
|
533
|
+
kind: "json",
|
|
534
|
+
label: "Claude Desktop",
|
|
535
|
+
path: configPath,
|
|
536
|
+
parentDir: path.dirname(configPath),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
if (target === "cursor") {
|
|
540
|
+
const override = process.env.VIVEWORKER_MCP_CURSOR_CONFIG_FILE || "";
|
|
541
|
+
const defaultPath = path.join(os.homedir(), ".cursor", "mcp.json");
|
|
542
|
+
const configPath = resolvePath(override || defaultPath);
|
|
543
|
+
return {
|
|
544
|
+
kind: "json",
|
|
545
|
+
label: "Cursor",
|
|
546
|
+
path: configPath,
|
|
547
|
+
parentDir: path.dirname(configPath),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (target === "codex") {
|
|
551
|
+
const override = process.env.VIVEWORKER_MCP_CODEX_CONFIG_FILE || "";
|
|
552
|
+
const defaultPath = path.join(os.homedir(), ".codex", "config.toml");
|
|
553
|
+
const configPath = resolvePath(override || defaultPath);
|
|
554
|
+
return {
|
|
555
|
+
kind: "toml",
|
|
556
|
+
label: "Codex",
|
|
557
|
+
path: configPath,
|
|
558
|
+
parentDir: path.dirname(configPath),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function upsertJsonMcpConfig(currentText, serverConfig) {
|
|
565
|
+
let data = {};
|
|
566
|
+
if (currentText.trim()) {
|
|
567
|
+
try {
|
|
568
|
+
data = JSON.parse(currentText);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
throw new Error(`Unable to parse existing MCP JSON config: ${error.message}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
574
|
+
data = {};
|
|
575
|
+
}
|
|
576
|
+
const previous = JSON.stringify(data);
|
|
577
|
+
const mcpServers = data.mcpServers && typeof data.mcpServers === "object" && !Array.isArray(data.mcpServers)
|
|
578
|
+
? data.mcpServers
|
|
579
|
+
: {};
|
|
580
|
+
data.mcpServers = {
|
|
581
|
+
...mcpServers,
|
|
582
|
+
viveworker: serverConfig,
|
|
583
|
+
};
|
|
584
|
+
const text = `${JSON.stringify(data, null, 2)}\n`;
|
|
585
|
+
return {
|
|
586
|
+
changed: JSON.stringify(data) !== previous,
|
|
587
|
+
text,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function upsertCodexMcpConfig(currentText, serverConfig) {
|
|
592
|
+
const block = [
|
|
593
|
+
"[mcp_servers.viveworker]",
|
|
594
|
+
`command = ${tomlString(serverConfig.command)}`,
|
|
595
|
+
`args = [${serverConfig.args.map((arg) => tomlString(arg)).join(", ")}]`,
|
|
596
|
+
"",
|
|
597
|
+
].join("\n");
|
|
598
|
+
const withoutExisting = removeTomlTable(currentText, "mcp_servers.viveworker").replace(/\s+$/u, "");
|
|
599
|
+
const text = `${withoutExisting ? `${withoutExisting}\n\n` : ""}${block}`;
|
|
600
|
+
return {
|
|
601
|
+
changed: normalizeConfigText(text) !== normalizeConfigText(currentText),
|
|
602
|
+
text,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function removeTomlTable(text, tableName) {
|
|
607
|
+
const lines = String(text || "").split(/\r?\n/u);
|
|
608
|
+
const out = [];
|
|
609
|
+
let skipping = false;
|
|
610
|
+
const header = `[${tableName}]`;
|
|
611
|
+
for (const line of lines) {
|
|
612
|
+
const trimmed = line.trim();
|
|
613
|
+
if (trimmed === header) {
|
|
614
|
+
skipping = true;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (skipping && /^\[[^\]]+\]\s*$/u.test(trimmed)) {
|
|
618
|
+
skipping = false;
|
|
619
|
+
}
|
|
620
|
+
if (!skipping) {
|
|
621
|
+
out.push(line);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return out.join("\n");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function tomlString(value) {
|
|
628
|
+
return JSON.stringify(String(value || ""));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function normalizeConfigText(value) {
|
|
632
|
+
return String(value || "").replace(/\r\n/gu, "\n").trim();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function readOptionalText(filePath) {
|
|
636
|
+
try {
|
|
637
|
+
return await fs.readFile(filePath, "utf8");
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (error?.code === "ENOENT") {
|
|
640
|
+
return "";
|
|
641
|
+
}
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function confirmCli(prompt) {
|
|
647
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
648
|
+
throw new Error("Refusing to write MCP config without an interactive terminal. Re-run with --yes or --dry-run.");
|
|
649
|
+
}
|
|
650
|
+
const rl = createReadlineInterface({
|
|
651
|
+
input: process.stdin,
|
|
652
|
+
output: process.stdout,
|
|
653
|
+
});
|
|
654
|
+
try {
|
|
655
|
+
const answer = await rl.question(prompt);
|
|
656
|
+
return /^y(es)?$/iu.test(String(answer || "").trim());
|
|
657
|
+
} finally {
|
|
658
|
+
rl.close();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
414
662
|
async function runEnableClaude(cliOptions) {
|
|
415
663
|
const setup = await loadExistingSetup(cliOptions);
|
|
416
664
|
const settingsFile = resolvePath(
|
|
@@ -1397,6 +1645,9 @@ function parseArgs(argv) {
|
|
|
1397
1645
|
autoScoutHarness: "auto",
|
|
1398
1646
|
autoScoutSubmolts: "",
|
|
1399
1647
|
autoScoutMaxDaily: 0,
|
|
1648
|
+
mcpTarget: "",
|
|
1649
|
+
mcpDryRun: false,
|
|
1650
|
+
yes: false,
|
|
1400
1651
|
};
|
|
1401
1652
|
|
|
1402
1653
|
if (argv[0] && !argv[0].startsWith("-")) {
|
|
@@ -1552,6 +1803,13 @@ function parseArgs(argv) {
|
|
|
1552
1803
|
} else if (arg === "--auto-scout-max-daily") {
|
|
1553
1804
|
parsed.autoScoutMaxDaily = Number(next) || 0;
|
|
1554
1805
|
index += 1;
|
|
1806
|
+
} else if (arg === "--target") {
|
|
1807
|
+
parsed.mcpTarget = next;
|
|
1808
|
+
index += 1;
|
|
1809
|
+
} else if (arg === "--dry-run") {
|
|
1810
|
+
parsed.mcpDryRun = true;
|
|
1811
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
1812
|
+
parsed.yes = true;
|
|
1555
1813
|
} else if (arg === "--help" || arg === "-h") {
|
|
1556
1814
|
parsed.command = "help";
|
|
1557
1815
|
} else {
|
|
@@ -1579,6 +1837,8 @@ ${t(locale, "cli.help.commands")}
|
|
|
1579
1837
|
${t(locale, "cli.help.status")}
|
|
1580
1838
|
${t(locale, "cli.help.doctor")}
|
|
1581
1839
|
${t(locale, "cli.help.update")}
|
|
1840
|
+
mcp Start the stdio MCP server
|
|
1841
|
+
mcp config Print MCP client config snippets
|
|
1582
1842
|
|
|
1583
1843
|
${t(locale, "cli.help.commonOptions")}
|
|
1584
1844
|
--port <n>
|
|
@@ -1607,6 +1867,7 @@ ${t(locale, "cli.help.featureOptions")}
|
|
|
1607
1867
|
${t(locale, "cli.help.enableA2a")}
|
|
1608
1868
|
${t(locale, "cli.help.enableMoltbook")}
|
|
1609
1869
|
${t(locale, "cli.help.enableScout")}
|
|
1870
|
+
enable mcp --target <claude|cursor|codex|all> [--dry-run|--yes]
|
|
1610
1871
|
${t(locale, "cli.help.doctorFix")}
|
|
1611
1872
|
`);
|
|
1612
1873
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: viveworker-control-plane
|
|
3
|
+
description: Route AI agent operations through the user's viveworker mobile control plane. Use when Codex has access to the viveworker MCP tools and needs to notify the phone, ask the user a question, request human approval, share a workspace deliverable through File Share, hand off context to another Codex/Claude/inbox thread, or delegate a bounded task to a registered A2A target. Especially use for externally visible, irreversible, sensitive, or cross-agent actions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# viveworker Control Plane
|
|
7
|
+
|
|
8
|
+
Use viveworker as the mobile control surface around agent work. Keep normal local work moving, but route decisions, confirmations, handoffs, deliverables, and delegation through the paired phone when that improves safety or flow.
|
|
9
|
+
|
|
10
|
+
## First Check
|
|
11
|
+
|
|
12
|
+
- If tool availability is uncertain, call `viveworker_status` first.
|
|
13
|
+
- If viveworker MCP is unavailable, do not pretend the action happened. Tell the user to run `npx viveworker enable mcp --target codex` or add the config from `npx viveworker mcp config`.
|
|
14
|
+
- Do not use viveworker for every routine local read/edit. Use it when a human decision, mobile notification, external handoff, or durable record is useful.
|
|
15
|
+
|
|
16
|
+
## First-Run Onboarding
|
|
17
|
+
|
|
18
|
+
Use this flow when the user asks to set up viveworker, pair a phone, install the control plane, or "make viveworker work here".
|
|
19
|
+
|
|
20
|
+
1. Check whether MCP tools are available.
|
|
21
|
+
- If available, call `viveworker_status` first and use the result to decide what is missing.
|
|
22
|
+
- If unavailable, explain that the MCP entry is not loaded yet and guide the user through `npx viveworker enable mcp --target codex`, then ask them to restart Codex.
|
|
23
|
+
2. If the bridge is not running or no local setup exists, ask for confirmation before running or recommending `npx viveworker setup`.
|
|
24
|
+
- Do not auto-run setup silently. It can install local certificates, write config, register launchd services, and change agent hooks.
|
|
25
|
+
- If the user explicitly asks you to execute setup, use the normal shell tool with clear confirmation, not MCP.
|
|
26
|
+
3. If setup exists but no phone is trusted, guide the user to run `npx viveworker pair` and open the pairing URL on the phone.
|
|
27
|
+
4. Ask the user to allow notifications from the Home Screen app when prompted.
|
|
28
|
+
5. Re-check with `viveworker_status`.
|
|
29
|
+
6. Send a final smoke notification with `viveworker_notify`.
|
|
30
|
+
7. Summarize what is ready: bridge, pairing, notifications, Remote connection, File Share, A2A, and Moltbook where applicable.
|
|
31
|
+
|
|
32
|
+
Keep onboarding calm and step-by-step. Do not make the user debug config files unless the status output points there.
|
|
33
|
+
|
|
34
|
+
## Tool Selection
|
|
35
|
+
|
|
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_ask` when the next step depends on a human preference, missing requirement, or choice that should not be guessed.
|
|
38
|
+
- Use `viveworker_request_approval` before actions that are externally visible, hard to undo, risky, sensitive, delegated, paid, or likely to surprise the user.
|
|
39
|
+
- 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`.
|
|
40
|
+
- Use `viveworker_thread_share` when context should move to another Codex session, Claude session, or viveworker inbox while preserving a human-visible handoff.
|
|
41
|
+
- Use `viveworker_send_a2a_task` only for registered A2A target aliases and bounded tasks with clear acceptance criteria.
|
|
42
|
+
|
|
43
|
+
## Approval And Question Style
|
|
44
|
+
|
|
45
|
+
- Keep phone prompts short and concrete.
|
|
46
|
+
- Include the action, expected result, and specific risk.
|
|
47
|
+
- Include relevant file refs, but never include secrets, tokens, private keys, or unnecessary file contents.
|
|
48
|
+
- Treat timeout, rejection, or transport failure as "not approved". Do not continue as if approval was granted.
|
|
49
|
+
- After the tool returns, summarize the decision or returned artifact to the user.
|
|
50
|
+
|
|
51
|
+
## File Share Rules
|
|
52
|
+
|
|
53
|
+
- Share only files inside the workspace root.
|
|
54
|
+
- Do not share `.env`, credential files, private keys, `.ssh`, `.aws`, `.gnupg`, or secret-looking paths.
|
|
55
|
+
- Prefer sharing final deliverables, not internal source files, unless the user explicitly asks.
|
|
56
|
+
- For unsupported file types, explain the accepted types and suggest exporting to HTML, PDF, image, or CSV first.
|
|
57
|
+
- Do not use File Share to bypass a user's explicit request not to upload or publish content.
|
|
58
|
+
|
|
59
|
+
## Thread Share Rules
|
|
60
|
+
|
|
61
|
+
- Use `targetConversationId` when the user names a specific thread.
|
|
62
|
+
- Use `targetTool` when the user says "send this to Codex", "share with Claude", or "put this in the inbox" without a specific thread ID.
|
|
63
|
+
- Send a compact handoff: current goal, decisions made, blockers, and concrete next action.
|
|
64
|
+
- Attach `contextFiles` only when they help the receiver act.
|
|
65
|
+
|
|
66
|
+
## A2A Delegation Rules
|
|
67
|
+
|
|
68
|
+
- Use only aliases from `~/.viveworker/a2a-targets.json`; never inline API keys in tool arguments.
|
|
69
|
+
- Keep delegated tasks self-contained and limited in scope.
|
|
70
|
+
- Include success criteria, constraints, and any deadline or budget.
|
|
71
|
+
- Ask for approval before sending, even if the task looks harmless.
|
|
72
|
+
- Do not send secrets, private repo data, or local paths unless the user clearly approves that exposure.
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
Ask before a risky action:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"title": "Approve release publish",
|
|
81
|
+
"message": "Publish viveworker 0.8.0 to npm. Risk: this makes the package public and hard to fully undo.",
|
|
82
|
+
"approvalKind": "release"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Share a deliverable:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"path": "/workspace/report.html",
|
|
91
|
+
"workspaceRoot": "/workspace",
|
|
92
|
+
"expiresDays": "3"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Delegate through A2A:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"target": "reviewer",
|
|
101
|
+
"instruction": "Review the remote connection security notes and return only concrete risks with file references.",
|
|
102
|
+
"metadata": { "scope": "security-review" }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "viveworker Control Plane"
|
|
3
|
+
short_description: "Route AI agent decisions through viveworker"
|
|
4
|
+
default_prompt: "Use $viveworker-control-plane to route approvals, questions, file shares, thread handoffs, and A2A delegation through viveworker."
|
|
5
|
+
dependencies:
|
|
6
|
+
tools:
|
|
7
|
+
- type: "mcp"
|
|
8
|
+
value: "viveworker"
|
|
9
|
+
description: "Local viveworker stdio MCP server"
|
|
10
|
+
transport: "stdio"
|
|
11
|
+
policy:
|
|
12
|
+
allow_implicit_invocation: true
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# viveworker MCP guide for Claude
|
|
2
|
+
|
|
3
|
+
Use viveworker as the mobile control plane for this Claude session when the user wants phone-based questions, approvals, file sharing, thread sharing, or A2A delegation.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
For Claude Desktop, the user should install the MCP entry once:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx viveworker enable mcp --target claude
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then restart Claude Desktop.
|
|
14
|
+
|
|
15
|
+
For Claude Code, the MCP config is separate from Claude Desktop. Install it once with:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
claude mcp add --scope user viveworker -- npx viveworker mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then restart the Claude Code session. If the tools are not available, ask the user to run the setup command for the Claude surface they are using.
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
- `viveworker_status` checks bridge, pairing, Remote connection, A2A, File Share, and Moltbook state.
|
|
26
|
+
- `viveworker_notify` sends an informational phone notification and records a timeline entry.
|
|
27
|
+
- `viveworker_ask` asks the paired phone a question and waits for the answer.
|
|
28
|
+
- `viveworker_request_approval` asks the phone to approve or reject a proposed action.
|
|
29
|
+
- `viveworker_share_file` uploads a workspace file to File Share after phone approval.
|
|
30
|
+
- `viveworker_thread_share` shares context into another Codex / Claude / inbox thread.
|
|
31
|
+
- `viveworker_send_a2a_task` sends a task to a registered A2A target after phone approval.
|
|
32
|
+
|
|
33
|
+
## When to use viveworker
|
|
34
|
+
|
|
35
|
+
- If the user says "ask me on my phone", "スマホに聞いて", or a short decision blocks progress, use `viveworker_ask`.
|
|
36
|
+
- If the user asks you to proceed with a risky, external, irreversible, payment-related, or user-visible action, use `viveworker_request_approval`.
|
|
37
|
+
- If the user asks for a report, prototype, screenshot, PDF, CSV, or standalone HTML to become a shareable link, use `viveworker_share_file`.
|
|
38
|
+
- If the user says "share this with Codex/Claude", "Aの内容をBに共有して", or wants context handed to another session, use `viveworker_thread_share`.
|
|
39
|
+
- If the user wants another registered agent to do work, use `viveworker_send_a2a_task`.
|
|
40
|
+
|
|
41
|
+
## Prompting rules
|
|
42
|
+
|
|
43
|
+
- Keep phone prompts short and concrete.
|
|
44
|
+
- Include the action, why it matters, expected outcome, and clear choices.
|
|
45
|
+
- Do not send secrets, private keys, `.env` content, credentials, or unnecessary file contents.
|
|
46
|
+
- Treat timeout, rejection, or missing response as not approved.
|
|
47
|
+
- Do not spam the phone for routine local reads or low-risk edits unless the user asked for mobile confirmation.
|
|
48
|
+
|
|
49
|
+
## Thread sharing
|
|
50
|
+
|
|
51
|
+
Thread Share is useful for handoffs such as:
|
|
52
|
+
|
|
53
|
+
- "Tell Codex what we just decided."
|
|
54
|
+
- "Share this Claude result with the Codex thread."
|
|
55
|
+
- "Aの内容をBに共有して。"
|
|
56
|
+
|
|
57
|
+
Send a compact summary with the decision, relevant context, and the exact next action expected from the recipient.
|
|
58
|
+
|
|
59
|
+
Claude may not automatically notice newly shared messages from another session. If the user says something arrived or asks you to pull shared context, inspect the viveworker timeline/inbox surface available in the current environment, or ask the user to open the relevant shared item.
|
|
60
|
+
|
|
61
|
+
## Safety defaults
|
|
62
|
+
|
|
63
|
+
- MCP is a control-plane surface, not a shell executor.
|
|
64
|
+
- Do not run shell strings through MCP.
|
|
65
|
+
- File Share and A2A task sending require phone approval.
|
|
66
|
+
- Use registered A2A aliases only; do not ask the user to paste API keys into prompts.
|
|
67
|
+
- Prefer `viveworker_status` before troubleshooting a missing notification, stale pairing, or remote connectivity issue.
|
package/web/app.css
CHANGED
|
@@ -77,6 +77,20 @@ code {
|
|
|
77
77
|
background: rgba(156, 181, 197, 0.12);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/*
|
|
81
|
+
* Reset the inline-`<code>` look when the element is wrapped by `<pre>`.
|
|
82
|
+
* Without this, a multi-line code block ends up with the per-`<code>`
|
|
83
|
+
* padding + rounded background applied to each visual line individually
|
|
84
|
+
* (the inline element is line-broken and the browser paints the background
|
|
85
|
+
* once per line), which made fenced code blocks look like a stack of
|
|
86
|
+
* inline code spans on the phone PWA.
|
|
87
|
+
*/
|
|
88
|
+
pre code {
|
|
89
|
+
padding: 0;
|
|
90
|
+
border-radius: 0;
|
|
91
|
+
background: transparent;
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
#app {
|
|
81
95
|
width: 100vw;
|
|
82
96
|
max-width: 100vw;
|
|
@@ -1054,6 +1068,48 @@ code {
|
|
|
1054
1068
|
word-break: break-all;
|
|
1055
1069
|
}
|
|
1056
1070
|
|
|
1071
|
+
.settings-external-link {
|
|
1072
|
+
max-width: 100%;
|
|
1073
|
+
display: inline-flex;
|
|
1074
|
+
align-items: center;
|
|
1075
|
+
justify-content: flex-end;
|
|
1076
|
+
gap: 0.32rem;
|
|
1077
|
+
color: rgba(121, 196, 255, 0.9);
|
|
1078
|
+
text-decoration: none;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
.settings-external-link:hover,
|
|
1082
|
+
.settings-external-link:focus-visible {
|
|
1083
|
+
color: rgba(176, 222, 255, 0.98);
|
|
1084
|
+
text-decoration: none;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.settings-external-link:focus-visible {
|
|
1088
|
+
outline: 1px solid rgba(121, 196, 255, 0.42);
|
|
1089
|
+
outline-offset: 3px;
|
|
1090
|
+
border-radius: 6px;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.settings-external-link__label {
|
|
1094
|
+
min-width: 0;
|
|
1095
|
+
overflow: hidden;
|
|
1096
|
+
text-overflow: ellipsis;
|
|
1097
|
+
white-space: nowrap;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
.settings-external-link__icon {
|
|
1101
|
+
width: 0.86rem;
|
|
1102
|
+
height: 0.86rem;
|
|
1103
|
+
flex: 0 0 auto;
|
|
1104
|
+
opacity: 0.72;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.settings-external-link__icon svg {
|
|
1108
|
+
width: 100%;
|
|
1109
|
+
height: 100%;
|
|
1110
|
+
display: block;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1057
1113
|
.settings-row__chevron {
|
|
1058
1114
|
width: 0.95rem;
|
|
1059
1115
|
height: 0.95rem;
|
|
@@ -2608,6 +2664,11 @@ code {
|
|
|
2608
2664
|
line-height: 1.38;
|
|
2609
2665
|
}
|
|
2610
2666
|
|
|
2667
|
+
.timeline-entry--kind-command-event .timeline-entry__title {
|
|
2668
|
+
font-size: 0.94rem;
|
|
2669
|
+
line-height: 1.38;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2611
2672
|
.timeline-entry__summary {
|
|
2612
2673
|
color: var(--muted);
|
|
2613
2674
|
line-height: 1.5;
|
|
@@ -2635,6 +2696,52 @@ code {
|
|
|
2635
2696
|
line-height: 1.3;
|
|
2636
2697
|
}
|
|
2637
2698
|
|
|
2699
|
+
.timeline-entry__command {
|
|
2700
|
+
width: 100%;
|
|
2701
|
+
margin: 0;
|
|
2702
|
+
padding: 0.62rem 0.72rem;
|
|
2703
|
+
border-radius: 0.85rem;
|
|
2704
|
+
border: 1px solid rgba(156, 181, 197, 0.14);
|
|
2705
|
+
background:
|
|
2706
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.015)),
|
|
2707
|
+
rgba(3, 9, 12, 0.62);
|
|
2708
|
+
color: rgba(236, 248, 255, 0.88);
|
|
2709
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
2710
|
+
font-size: 0.78rem;
|
|
2711
|
+
line-height: 1.45;
|
|
2712
|
+
overflow-x: auto;
|
|
2713
|
+
white-space: pre-wrap;
|
|
2714
|
+
overflow-wrap: anywhere;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
.timeline-entry__command code {
|
|
2718
|
+
font: inherit;
|
|
2719
|
+
color: inherit;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
.detail-command-block {
|
|
2723
|
+
width: 100%;
|
|
2724
|
+
margin: 0;
|
|
2725
|
+
padding: 0.82rem 0.88rem;
|
|
2726
|
+
border-radius: 1rem;
|
|
2727
|
+
border: 1px solid rgba(156, 181, 197, 0.15);
|
|
2728
|
+
background:
|
|
2729
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.015)),
|
|
2730
|
+
rgba(3, 9, 12, 0.68);
|
|
2731
|
+
color: rgba(236, 248, 255, 0.9);
|
|
2732
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
2733
|
+
font-size: 0.86rem;
|
|
2734
|
+
line-height: 1.55;
|
|
2735
|
+
overflow-x: auto;
|
|
2736
|
+
white-space: pre-wrap;
|
|
2737
|
+
overflow-wrap: anywhere;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
.detail-command-block code {
|
|
2741
|
+
font: inherit;
|
|
2742
|
+
color: inherit;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2638
2745
|
.timeline-entry__footer {
|
|
2639
2746
|
display: flex;
|
|
2640
2747
|
align-items: center;
|
|
@@ -2904,6 +3011,55 @@ code {
|
|
|
2904
3011
|
max-width: 100%;
|
|
2905
3012
|
display: block;
|
|
2906
3013
|
overflow-x: auto;
|
|
3014
|
+
border-collapse: collapse;
|
|
3015
|
+
margin: 0.4rem 0 0.6rem;
|
|
3016
|
+
font-size: 0.92rem;
|
|
3017
|
+
line-height: 1.45;
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
.detail-body.markdown table thead {
|
|
3021
|
+
background: rgba(255, 255, 255, 0.03);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
.detail-body.markdown table th,
|
|
3025
|
+
.detail-body.markdown table td {
|
|
3026
|
+
border: 1px solid rgba(156, 181, 197, 0.16);
|
|
3027
|
+
padding: 0.42rem 0.62rem;
|
|
3028
|
+
text-align: left;
|
|
3029
|
+
vertical-align: top;
|
|
3030
|
+
/* Wrap plain text at natural boundaries, but only break long unbreakable
|
|
3031
|
+
* runs as a last resort (instead of every column being forced narrow by
|
|
3032
|
+
* the previous `word-break: break-word`). The table itself already has
|
|
3033
|
+
* `overflow-x: auto`, so any cell that legitimately needs more width
|
|
3034
|
+
* triggers horizontal scroll on the whole table. */
|
|
3035
|
+
white-space: normal;
|
|
3036
|
+
word-break: normal;
|
|
3037
|
+
overflow-wrap: anywhere;
|
|
3038
|
+
/* Keep cells from collapsing to a single character per line under the
|
|
3039
|
+
* pressure of a wide inline `<code>` element. */
|
|
3040
|
+
min-width: 4rem;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
.detail-body.markdown table th {
|
|
3044
|
+
font-weight: 600;
|
|
3045
|
+
color: var(--text);
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
.detail-body.markdown table td {
|
|
3049
|
+
color: var(--muted);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
.detail-body.markdown table th code,
|
|
3053
|
+
.detail-body.markdown table td code {
|
|
3054
|
+
background: rgba(255, 255, 255, 0.05);
|
|
3055
|
+
padding: 0.05rem 0.32rem;
|
|
3056
|
+
border-radius: 6px;
|
|
3057
|
+
font-size: 0.86em;
|
|
3058
|
+
/* Inline code never breaks mid-token — keeps `npx viveworker enable mcp …`
|
|
3059
|
+
* legible on one line and lets the surrounding cell widen the table.
|
|
3060
|
+
* The phone reader gets horizontal scroll instead of the previous
|
|
3061
|
+
* char-per-line stack. */
|
|
3062
|
+
white-space: nowrap;
|
|
2907
3063
|
}
|
|
2908
3064
|
|
|
2909
3065
|
.detail-card {
|
|
@@ -3024,6 +3180,12 @@ code {
|
|
|
3024
3180
|
padding: 0.72rem;
|
|
3025
3181
|
}
|
|
3026
3182
|
|
|
3183
|
+
.detail-card--command {
|
|
3184
|
+
margin-top: 0.24rem;
|
|
3185
|
+
gap: 0.72rem;
|
|
3186
|
+
padding: 0.72rem;
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3027
3189
|
.detail-card--diff-thread {
|
|
3028
3190
|
margin-top: 0.24rem;
|
|
3029
3191
|
gap: 0.72rem;
|