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.
Files changed (35) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +141 -0
  3. package/package.json +7 -1
  4. package/plugins/viveworker-control-plane/.codex-plugin/plugin.json +49 -0
  5. package/plugins/viveworker-control-plane/.mcp.json +11 -0
  6. package/plugins/viveworker-control-plane/DISTRIBUTION.md +96 -0
  7. package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.png +0 -0
  8. package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.svg +39 -0
  9. package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +83 -0
  10. package/scripts/a2a-executor.mjs +261 -7
  11. package/scripts/a2a-handler.mjs +88 -0
  12. package/scripts/a2a-relay-client.mjs +6 -0
  13. package/scripts/lib/markdown-render.mjs +128 -1
  14. package/scripts/mcp-server.mjs +891 -0
  15. package/scripts/stats-cli.mjs +683 -0
  16. package/scripts/viveworker-bridge.mjs +1504 -128
  17. package/scripts/viveworker.mjs +262 -1
  18. package/skills/viveworker-control-plane/SKILL.md +104 -0
  19. package/skills/viveworker-control-plane/agents/openai.yaml +12 -0
  20. package/templates/CLAUDE.viveworker.md +67 -0
  21. package/web/app.css +162 -0
  22. package/web/app.js +1164 -101
  23. package/web/build-id.js +1 -0
  24. package/web/i18n.js +123 -15
  25. package/web/icons/apple-touch-icon.png +0 -0
  26. package/web/icons/viveworker-icon-192.png +0 -0
  27. package/web/icons/viveworker-icon-512.png +0 -0
  28. package/web/icons/viveworker-v-pulse.svg +16 -1
  29. package/web/index.html +2 -2
  30. package/web/remote-pairing/api-router.js +84 -0
  31. package/web/remote-pairing/transport.js +67 -2
  32. package/web/sw.js +16 -6
  33. package/web/icons/viveworker-beacon-v.svg +0 -19
  34. package/web/icons/viveworker-icon-1024.png +0 -0
  35. package/web/icons/viveworker-v-check.svg +0 -19
@@ -0,0 +1 @@
1
+ export const APP_BUILD_ID = "20260505-command-event-fast-approval-v1";
package/web/i18n.js CHANGED
@@ -48,6 +48,7 @@ const translations = {
48
48
  "common.choice": "Choice",
49
49
  "common.completion": "Completed",
50
50
  "common.fileEvent": "Files",
51
+ "common.commandEvent": "Command",
51
52
  "common.task": "Task",
52
53
  "common.approval": "Approval",
53
54
  "common.plan": "Plan",
@@ -144,6 +145,9 @@ const translations = {
144
145
  "detail.selectTitle": "Choose something from the list",
145
146
  "detail.selectCopy": "Pending approvals, plans, choices, and completed results will appear here.",
146
147
  "detail.loadingCopy": "Opening the detail view...",
148
+ "detail.loadFailedTitle": "Could not open the detail view",
149
+ "detail.loadFailedCopy": "The connection took too long or the item changed. You can retry without leaving this screen.",
150
+ "detail.loadRetry": "Retry",
147
151
  "detail.approvalRequested": "Approval requested.",
148
152
  "detail.planReady": "Plan is ready.",
149
153
  "detail.previousMessage": "Previous message",
@@ -186,10 +190,13 @@ const translations = {
186
190
  "detail.diffThread.copy.latest": ({ count }) =>
187
191
  `This thread has ${count} ${count === 1 ? "latest observed file change" : "latest observed file changes"}. Review each file below.`,
188
192
  "detail.fileEvent.read": "{provider} checked these files in this step.",
193
+ "detail.fileEvent.search": "{provider} searched in this step.",
194
+ "detail.fileEvent.command": "{provider} ran this command in this step.",
189
195
  "detail.fileEvent.write": "{provider} updated these files in this step.",
190
196
  "detail.fileEvent.create": "{provider} created these files in this step.",
191
197
  "detail.fileEvent.delete": "{provider} deleted these files in this step.",
192
198
  "detail.fileEvent.rename": "{provider} renamed these files in this step.",
199
+ "detail.commandEvent.copy": "{provider} ran this command in this step.",
193
200
  "detail.turnAbortedNotice": "This task was interrupted.",
194
201
  "detail.macOnlyChoice": "Please answer this type of input on the Mac.",
195
202
  "detail.detailUnavailable": "Detail not available.",
@@ -248,6 +255,7 @@ const translations = {
248
255
  "summary.plan": "Check the proposed plan before {provider} starts implementing it.",
249
256
  "summary.choice": "Pick the option {provider} needs to continue.",
250
257
  "summary.fileEvent": "Review the files {provider} checked or updated in this step.",
258
+ "summary.commandEvent": "Review the command {provider} ran in this step.",
251
259
  "summary.diffThread": "Review the file changes for this thread.",
252
260
  "summary.default": "Open the item to review its details.",
253
261
  "intent.approval": "Approve or reject this request.",
@@ -255,6 +263,7 @@ const translations = {
255
263
  "intent.choice": "Choose an answer so {provider} can continue.",
256
264
  "intent.completed": "Finished. Open to review the result.",
257
265
  "intent.fileEvent": "Review the files involved in this step.",
266
+ "intent.commandEvent": "Review the command that ran in this step.",
258
267
  "intent.diffThread": "Review the file changes for this thread.",
259
268
  "intent.ambientSuggestions": "Review suggested next steps and copy a prompt if you want to try one.",
260
269
  "intent.userMessage": "Open this message again.",
@@ -277,6 +286,7 @@ const translations = {
277
286
  "timeline.kindFilter.messages": "Messages",
278
287
  "timeline.kindFilter.suggestions": "Suggested next steps",
279
288
  "timeline.kindFilter.files": "Files",
289
+ "timeline.kindFilter.commands": "Commands",
280
290
  "timeline.kindFilter.approvals": "Approvals",
281
291
  "timeline.kindFilter.plans": "Plans",
282
292
  "timeline.kindFilter.choices": "Choices",
@@ -289,11 +299,15 @@ const translations = {
289
299
  "timeline.kindFilter.a2aRequests": "Task Requests",
290
300
  "timeline.kindFilter.a2aResults": "Task Results",
291
301
  "fileEvent.read": "Checked files",
302
+ "fileEvent.search": "Searched",
303
+ "fileEvent.command": "Ran command",
292
304
  "fileEvent.write": "Updated files",
293
305
  "fileEvent.create": "Created files",
294
306
  "fileEvent.delete": "Deleted files",
295
307
  "fileEvent.rename": "Renamed files",
296
308
  "fileEvent.timeline.read": ({ count }) => `Checked ${count} ${count === 1 ? "file" : "files"}`,
309
+ "fileEvent.timeline.search": ({ count }) => count > 0 ? `Searched ${count} ${count === 1 ? "path" : "paths"}` : "Searched",
310
+ "fileEvent.timeline.command": "Ran command",
297
311
  "fileEvent.timeline.write": ({ count }) => `Updated ${count} ${count === 1 ? "file" : "files"}`,
298
312
  "fileEvent.timeline.create": ({ count }) => `Created ${count} ${count === 1 ? "file" : "files"}`,
299
313
  "fileEvent.timeline.delete": ({ count }) => `Deleted ${count} ${count === 1 ? "file" : "files"}`,
@@ -573,6 +587,14 @@ const translations = {
573
587
  "a2a.task.deny": "Deny",
574
588
  "a2a.task.editHint": "Review the task request, then approve to execute or deny to reject.",
575
589
  "a2a.task.executor": "Execute with",
590
+ "a2a.task.paidDeliverable": "Paid deliverable",
591
+ "a2a.task.requestedTier": "Requested tier",
592
+ "a2a.task.requestedExecutor": "Requested executor",
593
+ "a2a.task.requestedModel": "Requested model",
594
+ "a2a.task.deliverableType": "Deliverable",
595
+ "a2a.task.price": "Price",
596
+ "a2a.task.payTo": "Pay to",
597
+ "a2a.task.unlockUrl": "Unlock URL",
576
598
  "a2a.executor.codex": "Codex",
577
599
  "a2a.executor.claude": "Claude",
578
600
  "settings.a2aExecutor.title": "Task Executor",
@@ -709,6 +731,7 @@ const translations = {
709
731
  "Try reopening the latest HTTPS URL in your browser, then re-add the Home Screen app if needed.",
710
732
  "error.remotePairingNeedsLanRefresh": "Remote connection needs a one-time refresh. Open viveworker once on the same Wi-Fi as your PC, then try off-LAN again.",
711
733
  "error.remotePairingUnavailable": "Remote connection is off or the PC is not reachable. Turn on Remote connection on the same Wi-Fi, then try again.",
734
+ "error.requestTimedOut": "The request took too long. Please try again.",
712
735
  "error.webPushDisabled": "Web Push is not enabled on the server.",
713
736
  "error.notificationsRequireHttps": "Notifications require HTTPS.",
714
737
  "error.pushUnsupported": "This browser does not support Web Push.",
@@ -758,6 +781,7 @@ const translations = {
758
781
  "server.title.assistantCommentary": "Update",
759
782
  "server.title.assistantFinal": "Final reply",
760
783
  "server.title.ambientSuggestions": "Suggested next steps",
784
+ "server.title.threadShare": "Thread Share",
761
785
  "server.action.review": "Review",
762
786
  "server.action.detail": "Detail",
763
787
  "server.action.select": "Choose",
@@ -767,6 +791,7 @@ const translations = {
767
791
  "server.action.implement": "Implement",
768
792
  "server.message.commandApprovalNeeded": "Command approval needed.",
769
793
  "server.message.commandLabel": "Command",
794
+ "server.message.toolLabel": "Tool",
770
795
  "server.message.filesLabel": "Files",
771
796
  "server.message.diffSummaryLabel": "Diff summary:",
772
797
  "server.message.autoPilotTrustedReadApproved": "Auto Pilot approved this trusted read command.",
@@ -809,6 +834,35 @@ const translations = {
809
834
  "server.confirm.reject": "This action will be rejected and {provider} will be told not to continue.",
810
835
  "server.pushTest.title": "viveworker test notification",
811
836
  "server.pushTest.body": "Notifications are working on this device.",
837
+ "server.push.threadShare.title": ({ source, target }) => `Thread Share: ${source} → ${target}`,
838
+ "server.push.threadShareFallback.title": "Thread Share could not reach the target",
839
+ "server.push.threadShareFallback.body": ({ target }) => `Codex thread "${target}" is not connected. The shared content was saved to the inbox.`,
840
+ "server.push.mcpNotify.title": "MCP notification",
841
+ "server.push.mcpQuestion.title": "Question from MCP",
842
+ "server.push.mcpApproval.title": "Approval from MCP",
843
+ "server.push.mcpShareFile.title": "File Share approval",
844
+ "server.push.mcpShareFile.body": ({ path, size }) => `An MCP client wants to upload ${path}${size ? ` (${size})` : ""} to File Share.`,
845
+ "server.push.mcpA2aTask.title": ({ target }) => `Send A2A task to ${target}`,
846
+ "server.push.mcpA2aTask.body": ({ target }) => `An MCP client wants to send a task to ${target}.`,
847
+ "server.push.paymentApproval.title": ({ amount }) => amount ? `Payment approval — ${amount} USDC` : "Payment approval",
848
+ "server.push.hazbasePayment.title": ({ amount }) => amount ? `Wallet payment approval — ${amount} USDC` : "Wallet payment approval",
849
+ "server.push.paymentApproval.body": ({ amount, network, resource }) =>
850
+ [`Amount: ${amount}`, network ? `Network: ${network}` : "", resource ? `Resource: ${resource}` : ""].filter(Boolean).join("\n"),
851
+ "server.push.moltbookReply.title": "Moltbook reply suggestion",
852
+ "server.push.moltbookReply.body": "A reply suggestion is ready in Moltbook.",
853
+ "server.push.moltbookDraft.replyTitle": ({ postTitle }) => postTitle ? `Moltbook reply draft: ${postTitle}` : "Moltbook reply draft",
854
+ "server.push.moltbookDraft.originalTitle": "Moltbook post draft",
855
+ "server.push.moltbookDraft.originalMorning": "Good morning. Share yesterday's highlights?",
856
+ "server.push.moltbookDraft.originalNoon": "Nice progress. Share your morning work?",
857
+ "server.push.moltbookDraft.originalEvening": "Great work today. Share what you built?",
858
+ "server.push.moltbookDraft.failedTitle": "Moltbook post failed",
859
+ "server.push.moltbookDraft.postedTitle": "Posted to Moltbook",
860
+ "server.push.moltbookDraft.postedReplyBody": ({ postTitle }) => `Reply posted${postTitle ? `: ${postTitle}` : ""}`,
861
+ "server.push.moltbookDraft.postedOriginalBody": ({ title }) => `Post published${title ? `: ${title}` : ""}`,
862
+ "server.push.a2aTask.title": "Incoming A2A task request",
863
+ "server.push.a2aRelayTask.title": "A2A task via relay",
864
+ "server.push.a2aResult.completedTitle": ({ instruction }) => `A2A completed: ${instruction}`,
865
+ "server.push.a2aResult.failedTitle": ({ instruction }) => `A2A failed: ${instruction}`,
812
866
  "server.page.notFoundTitle": "Not found",
813
867
  "server.page.notFoundBody": "This approval link does not exist.",
814
868
  "server.page.notFoundHint": "You can close this page safely.",
@@ -1058,6 +1112,7 @@ const translations = {
1058
1112
  "common.choice": "選択",
1059
1113
  "common.completion": "完了",
1060
1114
  "common.fileEvent": "ファイル",
1115
+ "common.commandEvent": "コマンド実行",
1061
1116
  "common.task": "タスク",
1062
1117
  "common.approval": "承認",
1063
1118
  "common.plan": "プラン",
@@ -1153,6 +1208,9 @@ const translations = {
1153
1208
  "detail.selectTitle": "一覧から項目を選んでください",
1154
1209
  "detail.selectCopy": "未対応の承認、プラン、選択、完了結果がここに表示されます。",
1155
1210
  "detail.loadingCopy": "詳細を開いています...",
1211
+ "detail.loadFailedTitle": "詳細を開けませんでした",
1212
+ "detail.loadFailedCopy": "接続に時間がかかっているか、項目の状態が変わりました。この画面のまま再試行できます。",
1213
+ "detail.loadRetry": "再試行",
1156
1214
  "detail.approvalRequested": "承認が必要です。",
1157
1215
  "detail.planReady": "プランの確認が必要です。",
1158
1216
  "detail.previousMessage": "ひとつ前のメッセージ",
@@ -1192,10 +1250,13 @@ const translations = {
1192
1250
  "detail.diffThread.copy.current": ({ count }) => `このスレッドで現在未ステージの変更があるファイルは ${count}件です。下でファイルごとに確認できます。`,
1193
1251
  "detail.diffThread.copy.latest": ({ count }) => `このスレッドで最後に観測した変更があるファイルは ${count}件です。下でファイルごとに確認できます。`,
1194
1252
  "detail.fileEvent.read": "このステップで確認したファイルです。",
1253
+ "detail.fileEvent.search": "このステップで検索した内容です。",
1254
+ "detail.fileEvent.command": "このステップで実行したコマンドです。",
1195
1255
  "detail.fileEvent.write": "このステップで更新したファイルです。",
1196
1256
  "detail.fileEvent.create": "このステップで作成したファイルです。",
1197
1257
  "detail.fileEvent.delete": "このステップで削除したファイルです。",
1198
1258
  "detail.fileEvent.rename": "このステップで名前を変更したファイルです。",
1259
+ "detail.commandEvent.copy": "このステップで実行したコマンドです。",
1199
1260
  "detail.turnAbortedNotice": "タスクを中断しました。",
1200
1261
  "detail.macOnlyChoice": "この種類の入力は Mac で回答してください。",
1201
1262
  "detail.detailUnavailable": "詳細は利用できません。",
@@ -1254,6 +1315,7 @@ const translations = {
1254
1315
  "summary.plan": "{provider} が実装を始める前に、提案されたプランを確認します。",
1255
1316
  "summary.choice": "{provider} が先へ進むために必要な選択肢を選びます。",
1256
1317
  "summary.fileEvent": "このステップで触れたファイルを確認します。",
1318
+ "summary.commandEvent": "このステップで実行したコマンドを確認します。",
1257
1319
  "summary.diffThread": "このスレッドのファイル変更を確認します。",
1258
1320
  "summary.default": "項目を開いて詳細を確認します。",
1259
1321
  "intent.approval": "このリクエストを承認するか拒否します。",
@@ -1261,6 +1323,7 @@ const translations = {
1261
1323
  "intent.choice": "{provider} が続行できるように回答を選びます。",
1262
1324
  "intent.completed": "完了済みです。結果を確認できます。",
1263
1325
  "intent.fileEvent": "このステップで触れたファイルを確認します。",
1326
+ "intent.commandEvent": "このステップで実行したコマンドを確認します。",
1264
1327
  "intent.diffThread": "このスレッドのファイル変更を確認します。",
1265
1328
  "intent.ambientSuggestions": "次にやる候補を確認して、必要なら prompt をコピーします。",
1266
1329
  "intent.userMessage": "このメッセージを開き直します。",
@@ -1283,6 +1346,7 @@ const translations = {
1283
1346
  "timeline.kindFilter.messages": "メッセージ",
1284
1347
  "timeline.kindFilter.suggestions": "候補提案",
1285
1348
  "timeline.kindFilter.files": "ファイル",
1349
+ "timeline.kindFilter.commands": "コマンド",
1286
1350
  "timeline.kindFilter.approvals": "承認",
1287
1351
  "timeline.kindFilter.plans": "プラン",
1288
1352
  "timeline.kindFilter.choices": "選択",
@@ -1301,11 +1365,15 @@ const translations = {
1301
1365
  "boot.status.loadingData": "データを読み込み中...",
1302
1366
  "boot.status.slowHint": "初回のリモート接続は数十秒かかることがあります。",
1303
1367
  "fileEvent.read": "ファイルを確認",
1368
+ "fileEvent.search": "検索",
1369
+ "fileEvent.command": "コマンド実行",
1304
1370
  "fileEvent.write": "ファイルを更新",
1305
1371
  "fileEvent.create": "ファイルを作成",
1306
1372
  "fileEvent.delete": "ファイルを削除",
1307
1373
  "fileEvent.rename": "ファイル名を変更",
1308
1374
  "fileEvent.timeline.read": ({ count }) => `ファイルを確認 ${count}件`,
1375
+ "fileEvent.timeline.search": ({ count }) => count > 0 ? `検索 ${count}件` : "検索",
1376
+ "fileEvent.timeline.command": "コマンド実行",
1309
1377
  "fileEvent.timeline.write": ({ count }) => `ファイルを更新 ${count}件`,
1310
1378
  "fileEvent.timeline.create": ({ count }) => `ファイルを作成 ${count}件`,
1311
1379
  "fileEvent.timeline.delete": ({ count }) => `ファイルを削除 ${count}件`,
@@ -1585,6 +1653,14 @@ const translations = {
1585
1653
  "a2a.task.deny": "拒否",
1586
1654
  "a2a.task.editHint": "タスクリクエストを確認し、承認して実行するか拒否してください。",
1587
1655
  "a2a.task.executor": "実行ツール",
1656
+ "a2a.task.paidDeliverable": "有料の成果物",
1657
+ "a2a.task.requestedTier": "希望ティア",
1658
+ "a2a.task.requestedExecutor": "希望実行先",
1659
+ "a2a.task.requestedModel": "希望モデル",
1660
+ "a2a.task.deliverableType": "成果物",
1661
+ "a2a.task.price": "価格",
1662
+ "a2a.task.payTo": "支払い先",
1663
+ "a2a.task.unlockUrl": "受け取りURL",
1588
1664
  "a2a.executor.codex": "Codex",
1589
1665
  "a2a.executor.claude": "Claude",
1590
1666
  "settings.a2aExecutor.title": "タスク実行ツール",
@@ -1613,8 +1689,8 @@ const translations = {
1613
1689
  "settings.a2aShare.status.unreachable": "到達不可",
1614
1690
  "settings.a2aShare.error": "上流への問い合わせに失敗しました: {reason}",
1615
1691
  "settings.wallet.title": "ウォレット",
1616
- "settings.wallet.subtitle": "サインイン / Passkey / Base アドレス",
1617
- "settings.wallet.copy": "ウォレットへのサインイン、Passkey、Base 系アドレスの管理を行います。",
1692
+ "settings.wallet.subtitle": "サインイン / パスキー / Base アドレス",
1693
+ "settings.wallet.copy": "ウォレットへのサインイン、パスキー、Base 系アドレスの管理を行います。",
1618
1694
  "settings.wallet.unavailable": "この viveworker では Wallet が設定されていません。",
1619
1695
  "settings.wallet.summary.title": "現在の状態",
1620
1696
  "settings.wallet.flow.title": "おすすめの順番",
@@ -1622,9 +1698,9 @@ const translations = {
1622
1698
  "settings.wallet.flow.copy": "A2A でタスクの報酬をやりとりするには、ウォレットを有効化する必要があります。",
1623
1699
  "settings.wallet.betaNotice": "ウォレット機能はベータテスト中です。この期間はテストネットでのみ支払い・受け取りを利用できます。",
1624
1700
  "settings.wallet.sessionExpired.title": "ウォレットセッションを更新しましょう",
1625
- "settings.wallet.sessionExpired.copy": "ウォレットのセッションが切れています。ワンタイムパスワードで再サインインすれば、Passkey Base Sepolia アドレスはそのまま使えます。",
1701
+ "settings.wallet.sessionExpired.copy": "ウォレットのセッションが切れています。ワンタイムパスワードで再サインインすれば、パスキーと Base Sepolia アドレスはそのまま使えます。",
1626
1702
  "settings.wallet.ready.title": "利用準備ができました",
1627
- "settings.wallet.ready.copy": "メールサインイン、Passkey、Base Sepolia ウォレットの準備ができています。",
1703
+ "settings.wallet.ready.copy": "メールサインイン、パスキー、Base Sepolia ウォレットの準備ができています。",
1628
1704
  "settings.wallet.ready.payoutIntro": "エージェントは下記アドレスを利用します:",
1629
1705
  "settings.wallet.ready.copyAddress": "タップでアドレスをコピー",
1630
1706
  "settings.wallet.advanced.title": "アカウント操作",
@@ -1639,8 +1715,8 @@ const translations = {
1639
1715
  "settings.wallet.step.signIn.copy": "メールにワンタイムパスワードを送り、届いた値を入力してウォレットにサインインします。",
1640
1716
  "settings.wallet.step.refreshSession.title": "セッションを更新",
1641
1717
  "settings.wallet.step.refreshSession.copy": "ウォレット操作を続ける前に、新しいワンタイムパスワードで再サインインします。",
1642
- "settings.wallet.step.passkey.title": "Passkey を登録",
1643
- "settings.wallet.step.passkey.copy": "この端末に Passkey を登録します。ウォレット発行や重要な操作で使います。",
1718
+ "settings.wallet.step.passkey.title": "パスキーを登録",
1719
+ "settings.wallet.step.passkey.copy": "この端末にパスキーを登録します。ウォレット発行や重要な操作で使います。",
1644
1720
  "settings.wallet.step.baseSepolia.title": "Base Sepolia ウォレットを発行",
1645
1721
  "settings.wallet.step.baseSepolia.copy": "現在の beta フローで使うテスト用ウォレットを発行します。まずはこれを用意するのがおすすめです。",
1646
1722
  "settings.wallet.step.base.title": "Base ウォレットを発行",
@@ -1655,7 +1731,7 @@ const translations = {
1655
1731
  "settings.hazbase.status.otpAwaitingVerify": "送信しました。メールを確認して、下のフィールドに入力してください。",
1656
1732
  "settings.hazbase.passkey.ready": "この端末で登録済み",
1657
1733
  "settings.hazbase.passkey.missing": "まだ登録されていません",
1658
- "settings.hazbase.passkey.localHostRequired": ".local ホスト名で viveworker を開くと Passkey を登録できます。",
1734
+ "settings.hazbase.passkey.localHostRequired": ".local ホスト名で viveworker を開くとパスキーを登録できます。",
1659
1735
  "settings.hazbase.wallet.missing": "未発行",
1660
1736
  "settings.hazbase.field.emailLabel": "メールアドレス",
1661
1737
  "settings.hazbase.field.emailPlaceholder": "you@example.com",
@@ -1666,16 +1742,16 @@ const translations = {
1666
1742
  "settings.hazbase.action.resendOtp": "再送信",
1667
1743
  "settings.hazbase.action.verifyOtp": "サインイン",
1668
1744
  "settings.hazbase.action.changeEmail": "メールアドレスを変更",
1669
- "settings.hazbase.action.registerPasskey": "Passkey登録",
1745
+ "settings.hazbase.action.registerPasskey": "パスキーを登録",
1670
1746
  "settings.hazbase.action.bootstrapBaseSepolia": "Base Sepolia発行",
1671
1747
  "settings.hazbase.action.bootstrapBase": "Base発行",
1672
1748
  "settings.hazbase.action.signOut": "ウォレットからログアウト",
1673
1749
  "settings.hazbase.logout.confirm.title": "ウォレットからログアウトしますか?",
1674
- "settings.hazbase.logout.confirm.copy": "再度アクセスするには、ワンタイムパスワードでのサインインが必要になります。ウォレットアドレスと passkey は同じアカウントに紐付いたまま残ります。",
1750
+ "settings.hazbase.logout.confirm.copy": "再度アクセスするには、ワンタイムパスワードでのサインインが必要になります。ウォレットアドレスとパスキーは同じアカウントに紐付いたまま残ります。",
1675
1751
  "settings.hazbase.logout.confirm.confirmLabel": "ログアウトする",
1676
1752
  "settings.hazbase.notice.otpRequested": "ワンタイムパスワードを送信しました。",
1677
1753
  "settings.hazbase.notice.otpVerified": "ウォレットにサインインしました。",
1678
- "settings.hazbase.notice.passkeyRegistered": "Passkeyを登録しました。",
1754
+ "settings.hazbase.notice.passkeyRegistered": "パスキーを登録しました。",
1679
1755
  "settings.hazbase.notice.walletBootstrapped": "chain {chainId} にウォレットを発行しました。",
1680
1756
  "settings.hazbase.notice.signedOut": "ウォレットからログアウトしました。",
1681
1757
  "settings.hazbase.notice.sessionRefreshStarted": "セッション更新を開始しました。ワンタイムパスワードを送信して再サインインしてください。",
@@ -1683,7 +1759,7 @@ const translations = {
1683
1759
  "settings.hazbase.prompt.otp": "ワンタイムパスワード",
1684
1760
  "settings.row.hazbaseStatus": "ウォレット",
1685
1761
  "settings.row.hazbaseEmail": "ウォレットのメール",
1686
- "settings.row.hazbasePasskey": "Passkey",
1762
+ "settings.row.hazbasePasskey": "パスキー",
1687
1763
  "settings.row.hazbaseBaseSepolia": "Base Sepoliaウォレット",
1688
1764
  "settings.row.hazbaseBase": "Baseウォレット",
1689
1765
  "settings.row.a2aShareStatus": "ステータス",
@@ -1721,6 +1797,7 @@ const translations = {
1721
1797
  "最新の HTTPS URL をブラウザで開き直して、必要ならホーム画面アプリを追加し直してください。",
1722
1798
  "error.remotePairingNeedsLanRefresh": "リモート接続の更新が必要です。一度 PC と同じ WiFi で viveworker を開いてから、もう一度 LAN 外で試してください。",
1723
1799
  "error.remotePairingUnavailable": "リモート接続が OFF、または PC に到達できません。同じ WiFi でリモート接続を ON にしてから、もう一度試してください。",
1800
+ "error.requestTimedOut": "リクエストに時間がかかりすぎています。もう一度試してください。",
1724
1801
  "error.webPushDisabled": "サーバー側で Web Push が有効ではありません。",
1725
1802
  "error.notificationsRequireHttps": "通知には HTTPS が必要です。",
1726
1803
  "error.pushUnsupported": "このブラウザは Web Push に対応していません。",
@@ -1756,7 +1833,7 @@ const translations = {
1756
1833
  "error.hazbaseSessionExpired": "ウォレットのセッションが切れています。Wallet 設定を開いて再サインインしてください。",
1757
1834
  "error.hazbaseEmailRequired": "先にメールアドレスを入力してください。",
1758
1835
  "error.hazbaseOtpRequired": "ワンタイムパスワードを入力してください。",
1759
- "error.hazbasePasskeyLocalHostRequired": ".local ホスト名で viveworker を開くと hazBase Passkey を使えます。",
1836
+ "error.hazbasePasskeyLocalHostRequired": ".local ホスト名で viveworker を開くと hazBase パスキーを使えます。",
1760
1837
  "error.hazbaseWalletAccountMissing": "先にこのチェーンのウォレットを発行してください。",
1761
1838
  "error.unsupportedChain": "このチェーンはここではサポートされていません。",
1762
1839
  "server.fallback.codexTask": "{provider} task",
@@ -1770,6 +1847,7 @@ const translations = {
1770
1847
  "server.title.assistantCommentary": "途中経過",
1771
1848
  "server.title.assistantFinal": "最終回答",
1772
1849
  "server.title.ambientSuggestions": "次の候補",
1850
+ "server.title.threadShare": "スレッド共有",
1773
1851
  "server.action.review": "判断する",
1774
1852
  "server.action.detail": "詳細",
1775
1853
  "server.action.select": "選ぶ",
@@ -1779,7 +1857,8 @@ const translations = {
1779
1857
  "server.action.implement": "実装する",
1780
1858
  "server.message.commandApprovalNeeded": "コマンド実行の承認が必要です。",
1781
1859
  "server.message.commandLabel": "コマンド",
1782
- "server.message.filesLabel": "Files",
1860
+ "server.message.toolLabel": "ツール",
1861
+ "server.message.filesLabel": "ファイル",
1783
1862
  "server.message.diffSummaryLabel": "差分サマリ:",
1784
1863
  "server.message.autoPilotTrustedReadApproved": "Auto Pilot がこの Trusted Read コマンドを自動承認しました。",
1785
1864
  "server.message.autoPilotTrustedReadSummary": "Auto Pilot が Trusted Read コマンドを自動承認しました。",
@@ -1788,8 +1867,8 @@ const translations = {
1788
1867
  "server.message.autoPilotTrustedWriteSummary": "承認の自動化が低リスクなファイル変更を自動承認しました。",
1789
1868
  "server.message.autoPilotTrustedWriteReason": "viveworker が現在のワークスペース向け低リスク書き込みポリシーに一致したため自動承認しました。",
1790
1869
  "server.message.fileApprovalNeeded": "ファイル変更の承認が必要です。",
1791
- "server.message.commandPrefix": ({ command }) => `Command: ${command}`,
1792
- "server.message.pathPrefix": ({ path }) => `Path: ${path}`,
1870
+ "server.message.commandPrefix": ({ command }) => `コマンド: ${command}`,
1871
+ "server.message.pathPrefix": ({ path }) => `パス: ${path}`,
1793
1872
  "server.message.approveOnMac": "Mac で承認してください: この端末の通知は codex.app の鏡像です。",
1794
1873
  "server.message.approvalNeededInCodex": "{provider} で承認が必要です。",
1795
1874
  "server.message.extraApprovals": ({ count }) => `追加の承認: ${count}`,
@@ -1821,6 +1900,35 @@ const translations = {
1821
1900
  "server.confirm.reject": "この操作を拒否して {provider} に続行しないよう伝えます。",
1822
1901
  "server.pushTest.title": "viveworker テスト通知",
1823
1902
  "server.pushTest.body": "この端末で通知が動作しています。",
1903
+ "server.push.threadShare.title": ({ source, target }) => `スレッド共有: ${source} → ${target}`,
1904
+ "server.push.threadShareFallback.title": "スレッド共有の送信先に接続できません",
1905
+ "server.push.threadShareFallback.body": ({ target }) => `Codex スレッド「${target}」に接続できません。共有内容は inbox に保存しました。`,
1906
+ "server.push.mcpNotify.title": "MCP 通知",
1907
+ "server.push.mcpQuestion.title": "MCP からの質問",
1908
+ "server.push.mcpApproval.title": "MCP からの承認",
1909
+ "server.push.mcpShareFile.title": "ファイル共有の確認",
1910
+ "server.push.mcpShareFile.body": ({ path, size }) => `MCP クライアントが ${path}${size ? ` (${size})` : ""} を File Share にアップロードしようとしています。`,
1911
+ "server.push.mcpA2aTask.title": ({ target }) => `${target} に A2A タスクを送信`,
1912
+ "server.push.mcpA2aTask.body": ({ target }) => `MCP クライアントが ${target} にタスクを送ろうとしています。`,
1913
+ "server.push.paymentApproval.title": ({ amount }) => amount ? `支払いの承認 — ${amount} USDC` : "支払いの承認",
1914
+ "server.push.hazbasePayment.title": ({ amount }) => amount ? `ウォレット支払いの承認 — ${amount} USDC` : "ウォレット支払いの承認",
1915
+ "server.push.paymentApproval.body": ({ amount, network, resource }) =>
1916
+ [`金額: ${amount}`, network ? `ネットワーク: ${network}` : "", resource ? `リソース: ${resource}` : ""].filter(Boolean).join("\n"),
1917
+ "server.push.moltbookReply.title": "Moltbook の返信案",
1918
+ "server.push.moltbookReply.body": "Moltbook に返信案が届いています。",
1919
+ "server.push.moltbookDraft.replyTitle": ({ postTitle }) => postTitle ? `Moltbook の返信案: ${postTitle}` : "Moltbook の返信案",
1920
+ "server.push.moltbookDraft.originalTitle": "Moltbook の投稿案",
1921
+ "server.push.moltbookDraft.originalMorning": "おはようございます。昨日の成果をシェアしませんか?",
1922
+ "server.push.moltbookDraft.originalNoon": "午前中の進捗をシェアしませんか?",
1923
+ "server.push.moltbookDraft.originalEvening": "今日の成果をシェアしませんか?",
1924
+ "server.push.moltbookDraft.failedTitle": "Moltbook への投稿に失敗しました",
1925
+ "server.push.moltbookDraft.postedTitle": "Moltbook に投稿しました",
1926
+ "server.push.moltbookDraft.postedReplyBody": ({ postTitle }) => `返信を投稿しました${postTitle ? `: ${postTitle}` : ""}`,
1927
+ "server.push.moltbookDraft.postedOriginalBody": ({ title }) => `投稿を公開しました${title ? `: ${title}` : ""}`,
1928
+ "server.push.a2aTask.title": "外部エージェントからタスク依頼",
1929
+ "server.push.a2aRelayTask.title": "A2A リレー経由のタスク依頼",
1930
+ "server.push.a2aResult.completedTitle": ({ instruction }) => `A2A 完了: ${instruction}`,
1931
+ "server.push.a2aResult.failedTitle": ({ instruction }) => `A2A 失敗: ${instruction}`,
1824
1932
  "server.page.notFoundTitle": "見つかりません",
1825
1933
  "server.page.notFoundBody": "この承認リンクは存在しません。",
1826
1934
  "server.page.notFoundHint": "このページは閉じて大丈夫です。",
Binary file
Binary file
Binary file
@@ -9,16 +9,31 @@
9
9
  <stop stop-color="#5ED6FF" stop-opacity="0.34"/>
10
10
  <stop offset="1" stop-color="#5ED6FF" stop-opacity="0"/>
11
11
  </radialGradient>
12
+ <linearGradient id="vFill" x1="264" y1="286" x2="778" y2="776" gradientUnits="userSpaceOnUse">
13
+ <stop stop-color="#FFFFFF"/>
14
+ <stop offset="0.5" stop-color="#F5FBFF"/>
15
+ <stop offset="1" stop-color="#E7F4F8"/>
16
+ </linearGradient>
17
+ <radialGradient id="vShade" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(512 674) rotate(90) scale(196 292)">
18
+ <stop stop-color="#CFEAF5" stop-opacity="0.46"/>
19
+ <stop offset="1" stop-color="#CFEAF5" stop-opacity="0"/>
20
+ </radialGradient>
12
21
  <filter id="soft" x="557" y="90" width="320" height="320" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
13
22
  <feGaussianBlur stdDeviation="24"/>
14
23
  </filter>
24
+ <filter id="vShadow" x="190" y="240" width="650" height="630" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
25
+ <feDropShadow dx="0" dy="24" stdDeviation="30" flood-color="#5ED6FF" flood-opacity="0.14"/>
26
+ </filter>
15
27
  </defs>
16
28
  <rect width="1024" height="1024" rx="224" fill="url(#bg)"/>
17
29
  <circle cx="717" cy="250" r="112" fill="url(#glow)"/>
18
30
  <g filter="url(#soft)">
19
31
  <circle cx="717" cy="250" r="58" fill="#7DD3FC" fill-opacity="0.36"/>
20
32
  </g>
21
- <path d="M290 302L469 731C487 773 537 773 555 731L734 302" stroke="#F6FBFF" stroke-width="104" stroke-linecap="round" stroke-linejoin="round"/>
33
+ <g filter="url(#vShadow)">
34
+ <path d="M270 292C235 304 220 342 237 376L421 729C458 800 566 800 603 729L787 376C804 342 789 304 754 292C719 280 684 301 670 335L534 657C525 679 499 679 490 657L354 335C340 301 305 280 270 292Z" fill="url(#vFill)"/>
35
+ <path d="M270 292C235 304 220 342 237 376L421 729C458 800 566 800 603 729L787 376C804 342 789 304 754 292C719 280 684 301 670 335L534 657C525 679 499 679 490 657L354 335C340 301 305 280 270 292Z" fill="url(#vShade)"/>
36
+ </g>
22
37
  <path d="M697 180C748 180 789 221 789 272" stroke="#7DD3FC" stroke-width="40" stroke-linecap="round"/>
23
38
  <path d="M667 120C752 120 821 189 821 274" stroke="#7DD3FC" stroke-width="28" stroke-linecap="round" opacity="0.92"/>
24
39
  </svg>
package/web/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
  <link rel="manifest" href="/manifest.webmanifest">
8
8
  <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
9
9
  <link rel="icon" type="image/png" sizes="192x192" href="/icons/viveworker-icon-192.png">
10
- <link rel="stylesheet" href="/app.css">
10
+ <link rel="stylesheet" href="/app.css?v=__VIVEWORKER_APP_BUILD_ID__">
11
11
  <style>
12
12
  .boot-splash {
13
13
  position: fixed;
@@ -126,6 +126,6 @@
126
126
  if (splash) splash.setAttribute("aria-label", `viveworker ${message}`);
127
127
  })();
128
128
  </script>
129
- <script type="module" src="/app.js?v=20260428-remote-token-refresh"></script>
129
+ <script type="module" src="/app.js?v=__VIVEWORKER_APP_BUILD_ID__"></script>
130
130
  </body>
131
131
  </html>
@@ -98,6 +98,10 @@ const DEFAULT_RELAY_TIMEOUT_MS = 60_000;
98
98
  * LAN is the fast path, and slow/unreachable LAN should become relay.
99
99
  */
100
100
  const DEFAULT_LAN_TIMEOUT_MS = 2_500;
101
+ const DEFAULT_STICKY_LAN_PROBE_TIMEOUT_MS = 350;
102
+ const RELAY_FAILURE_WINDOW_MS = 60_000;
103
+ const RELAY_FAILURE_THRESHOLD = 6;
104
+ const RELAY_CIRCUIT_BREAKER_MS = 60_000;
101
105
  const PAIRING_STATE_STORAGE_KEY = "viveworker.remote-pairing.state";
102
106
  const PAIRING_STATE_SCHEMA_VERSION = 2;
103
107
  const PAIRING_STATE_LEGACY_SCHEMA_VERSION = 1;
@@ -128,6 +132,12 @@ let _lastPairingStateStatus = null;
128
132
  /** Last transport route that completed successfully. */
129
133
  let _lastSuccessfulRoute = null;
130
134
 
135
+ /** Recent relay-level failures used to avoid burning relay/DO quota in loops. */
136
+ let _relayFailureAtMs = [];
137
+
138
+ /** If > now, relay attempts are intentionally skipped. */
139
+ let _relayCircuitOpenUntilMs = 0;
140
+
131
141
  function newTelemetry() {
132
142
  return {
133
143
  lanOk: 0,
@@ -404,6 +414,51 @@ function resetRelayClientForRetry() {
404
414
  _wakeUnbind = null;
405
415
  }
406
416
 
417
+ function closeRelayClient(_reason = "closed") {
418
+ if (_client) {
419
+ _telemetry.clientResets++;
420
+ try { _client.close(); } catch { /* ignore */ }
421
+ }
422
+ _client = null;
423
+ _clientPairingKey = null;
424
+ if (_wakeUnbind) {
425
+ try { _wakeUnbind(); } catch { /* ignore */ }
426
+ }
427
+ _wakeUnbind = null;
428
+ }
429
+
430
+ function relayCircuitDelayMs(opts = {}) {
431
+ return Math.max(0, _relayCircuitOpenUntilMs - nowMs(opts));
432
+ }
433
+
434
+ function relayCircuitError(opts = {}) {
435
+ const delayMs = relayCircuitDelayMs(opts);
436
+ const err = new Error(`remote relay temporarily cooled down (${Math.ceil(delayMs / 1000)}s)`);
437
+ err.name = "RemoteRelayCircuitOpenError";
438
+ err.code = "remote-relay-circuit-open";
439
+ err.retryAfterMs = delayMs;
440
+ return err;
441
+ }
442
+
443
+ function recordRelayFailure(err, opts = {}) {
444
+ const now = nowMs(opts);
445
+ _relayFailureAtMs = _relayFailureAtMs.filter((at) => now - at <= RELAY_FAILURE_WINDOW_MS);
446
+ _relayFailureAtMs.push(now);
447
+ if (_relayFailureAtMs.length < RELAY_FAILURE_THRESHOLD) return;
448
+ _relayCircuitOpenUntilMs = Math.max(_relayCircuitOpenUntilMs, now + RELAY_CIRCUIT_BREAKER_MS);
449
+ _relayFailureAtMs = [];
450
+ closeRelayClient("relay circuit open");
451
+ emitRoutingStatus("remote-cooled-down", opts, {
452
+ reason: err?.message || String(err || "relay failure"),
453
+ retryAfterMs: RELAY_CIRCUIT_BREAKER_MS,
454
+ });
455
+ }
456
+
457
+ function resetRelayFailureCircuit() {
458
+ _relayFailureAtMs = [];
459
+ _relayCircuitOpenUntilMs = 0;
460
+ }
461
+
407
462
  // ---------------------------------------------------------------------------
408
463
  // Response adapter — RpcResponse → minimal Fetch-Response shape
409
464
  // ---------------------------------------------------------------------------
@@ -500,6 +555,9 @@ async function attemptLanFetch(url, init, opts) {
500
555
  : await fetchPromise;
501
556
  _telemetry.lanOk++;
502
557
  _lastSuccessfulRoute = "lan";
558
+ _stickyRelayUntilMs = 0;
559
+ resetRelayFailureCircuit();
560
+ closeRelayClient("lan connected");
503
561
  emitRoutingStatus("lan-connected", opts, { url: String(url || "") });
504
562
  return { ok: true, response };
505
563
  } catch (err) {
@@ -524,6 +582,18 @@ async function attemptLanFetch(url, init, opts) {
524
582
  }
525
583
  }
526
584
 
585
+ async function attemptStickyLanProbe(url, init, opts) {
586
+ const probeTimeoutMs = opts.stickyLanProbeTimeoutMs ?? DEFAULT_STICKY_LAN_PROBE_TIMEOUT_MS;
587
+ const lan = await attemptLanFetch(url, init, {
588
+ ...opts,
589
+ lanTimeoutMs: probeTimeoutMs,
590
+ });
591
+ if (lan.ok) {
592
+ _stickyRelayUntilMs = 0;
593
+ }
594
+ return lan;
595
+ }
596
+
527
597
  function abortError(signal) {
528
598
  if (signal?.reason instanceof Error) {
529
599
  const err = signal.reason;
@@ -680,6 +750,9 @@ async function encodeFormDataForRelay(formData, signal) {
680
750
 
681
751
  async function attemptRelayFetch(url, init, opts) {
682
752
  emitRoutingStatus("remote-connecting", opts, { url: String(url || "") });
753
+ if (relayCircuitDelayMs(opts) > 0) {
754
+ return { ok: false, err: relayCircuitError(opts) };
755
+ }
683
756
  const client = await getOrInitClient(opts);
684
757
  if (!client) {
685
758
  return { ok: false, err: relayClientUnavailableError(_lastPairingStateStatus) };
@@ -724,6 +797,7 @@ async function attemptRelayFetch(url, init, opts) {
724
797
  });
725
798
  _telemetry.relayOk++;
726
799
  _lastSuccessfulRoute = "relay";
800
+ resetRelayFailureCircuit();
727
801
  emitRoutingStatus("remote-connected", opts, { url: String(url || "") });
728
802
  return { ok: true, response: adaptRpcResponse(rpcRes) };
729
803
  } catch (err) {
@@ -734,6 +808,7 @@ async function attemptRelayFetch(url, init, opts) {
734
808
  }
735
809
  _telemetry.relayFail++;
736
810
  _telemetry.lastRelayFailAt = nowMs(opts);
811
+ recordRelayFailure(err, opts);
737
812
  emitRoutingStatus("remote-failed", opts, {
738
813
  url: String(url || ""),
739
814
  reason: err?.message || String(err),
@@ -783,6 +858,8 @@ function nowMs(opts) {
783
858
  * onRouteStatus?: (event: { phase: string, atMs: number, url?: string, sticky?: boolean, reason?: string, state?: string, previousState?: string, code?: number, resumed?: boolean }) => void,
784
859
  * suppressRoutingStatus?: boolean,
785
860
  * preferRelayError?: boolean,
861
+ * probeLanWhileSticky?: boolean,
862
+ * stickyLanProbeTimeoutMs?: number,
786
863
  * }} [opts]
787
864
  * @returns {Promise<{
788
865
  * ok: boolean,
@@ -798,6 +875,10 @@ export async function routedFetch(url, init = {}, opts = {}) {
798
875
 
799
876
  // Sticky-relay path: LAN just failed, prefer relay for a while.
800
877
  if (_stickyRelayUntilMs > t) {
878
+ if (opts.probeLanWhileSticky === true) {
879
+ const lan = await attemptStickyLanProbe(url, init, opts);
880
+ if (lan.ok) return lan.response;
881
+ }
801
882
  emitRoutingStatus("remote-switching", opts, { url: String(url || ""), sticky: true });
802
883
  const r = await attemptRelayFetch(url, init, opts);
803
884
  if (r.ok) return r.response;
@@ -839,6 +920,7 @@ export function __getTelemetry() {
839
920
  lastRoute: _lastSuccessfulRoute,
840
921
  hasClient: Boolean(_client),
841
922
  clientPairingKey: _clientPairingKey,
923
+ relayCircuitOpenUntilMs: _relayCircuitOpenUntilMs,
842
924
  };
843
925
  }
844
926
 
@@ -864,6 +946,8 @@ export function __resetForTest() {
864
946
  _telemetry = newTelemetry();
865
947
  _lastPairingStateStatus = null;
866
948
  _lastSuccessfulRoute = null;
949
+ _relayFailureAtMs = [];
950
+ _relayCircuitOpenUntilMs = 0;
867
951
  }
868
952
 
869
953
  // Test-visible constants for tests that want to assert behavior at the