viveworker 0.4.8 → 0.4.9
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/package.json +1 -1
- package/scripts/a2a-executor.mjs +118 -30
- package/scripts/a2a-handler.mjs +4 -0
- package/scripts/a2a-relay-client.mjs +4 -0
- package/scripts/moltbook-api.mjs +5 -5
- package/scripts/viveworker-bridge.mjs +14 -2
- package/web/app.css +126 -0
- package/web/app.js +66 -21
- package/web/i18n.js +16 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Local mobile companion for Codex Desktop and Claude Desktop — approvals, code review, Moltbook drafts, and A2A (Agent-to-Agent) task relay on your LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/scripts/a2a-executor.mjs
CHANGED
|
@@ -14,6 +14,67 @@ import { completeA2ATask, failA2ATask } from "./a2a-handler.mjs";
|
|
|
14
14
|
|
|
15
15
|
const APP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
16
16
|
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// PATH augmentation for launchd environments
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build an augmented PATH that includes nvm, homebrew, and other common
|
|
23
|
+
* directories so spawned processes (claude, codex) can find `node`, etc.
|
|
24
|
+
*
|
|
25
|
+
* Under launchd the inherited PATH is minimal (/usr/bin:/bin:/usr/sbin:/sbin).
|
|
26
|
+
* We prepend well-known bin directories so child processes work correctly.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} [binPath] - Resolved path to the binary being spawned;
|
|
29
|
+
* its parent directory is prepended to PATH.
|
|
30
|
+
*/
|
|
31
|
+
function buildAugmentedPath(binPath) {
|
|
32
|
+
const extra = [];
|
|
33
|
+
|
|
34
|
+
// Include the directory of the resolved binary itself
|
|
35
|
+
if (binPath && binPath !== "codex" && binPath !== "claude") {
|
|
36
|
+
extra.push(path.dirname(binPath));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// nvm — find the active or latest node version's bin directory
|
|
40
|
+
const nvmBase = path.join(os.homedir(), ".nvm", "versions", "node");
|
|
41
|
+
if (existsSync(nvmBase)) {
|
|
42
|
+
// Prefer NVM_BIN if set (interactive shell), otherwise scan versions
|
|
43
|
+
if (process.env.NVM_BIN && existsSync(process.env.NVM_BIN)) {
|
|
44
|
+
extra.push(process.env.NVM_BIN);
|
|
45
|
+
} else {
|
|
46
|
+
const ls = spawnSync("ls", [nvmBase], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
47
|
+
if (ls.status === 0 && ls.stdout) {
|
|
48
|
+
const versions = ls.stdout.trim().split("\n").filter(Boolean).reverse();
|
|
49
|
+
for (const v of versions) {
|
|
50
|
+
const binDir = path.join(nvmBase, v, "bin");
|
|
51
|
+
if (existsSync(path.join(binDir, "node"))) {
|
|
52
|
+
extra.push(binDir);
|
|
53
|
+
break; // use the latest version that has node
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Homebrew and common paths
|
|
61
|
+
for (const p of ["/opt/homebrew/bin", "/usr/local/bin"]) {
|
|
62
|
+
if (existsSync(p)) extra.push(p);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const basePath = process.env.PATH || "/usr/bin:/bin:/usr/sbin:/sbin";
|
|
66
|
+
// Deduplicate while preserving order
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
const parts = [];
|
|
69
|
+
for (const dir of [...extra, ...basePath.split(":")]) {
|
|
70
|
+
if (dir && !seen.has(dir)) {
|
|
71
|
+
seen.add(dir);
|
|
72
|
+
parts.push(dir);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return parts.join(":");
|
|
76
|
+
}
|
|
77
|
+
|
|
17
78
|
/**
|
|
18
79
|
* Try to find the claude binary by walking well-known locations.
|
|
19
80
|
* Returns the absolute path if found, or null.
|
|
@@ -98,9 +159,11 @@ const EXEC_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
98
159
|
* @param {object} helpers
|
|
99
160
|
* @param {Function} helpers.recordTimelineEntry
|
|
100
161
|
* @param {Function} helpers.saveState
|
|
162
|
+
* @param {Function} [helpers.recordHistoryItem] - Optional history-item recorder (adds to completed inbox)
|
|
163
|
+
* @param {Function} [helpers.deliverWebPushItem] - Optional web push delivery helper
|
|
101
164
|
* @param {string} [executor] - "codex" or "claude" (auto-detect if omitted)
|
|
102
165
|
*/
|
|
103
|
-
export async function executeA2ATask(task, config, runtime, state, { recordTimelineEntry, saveState }, executor) {
|
|
166
|
+
export async function executeA2ATask(task, config, runtime, state, { recordTimelineEntry, recordHistoryItem, saveState, deliverWebPushItem }, executor) {
|
|
104
167
|
const instruction = task.instruction || "";
|
|
105
168
|
if (!instruction) {
|
|
106
169
|
failA2ATask(task, "No instruction provided");
|
|
@@ -126,38 +189,59 @@ export async function executeA2ATask(task, config, runtime, state, { recordTimel
|
|
|
126
189
|
console.error(`[a2a-exec] Task ${task.id} failed via ${executor}: ${error.message}`);
|
|
127
190
|
}
|
|
128
191
|
|
|
129
|
-
// Record completion in timeline
|
|
192
|
+
// Record completion in timeline AND history (history drives completed inbox list).
|
|
193
|
+
const resultEntry = {
|
|
194
|
+
stableId: `a2a_task_result:${task.id}`,
|
|
195
|
+
token: task.token,
|
|
196
|
+
kind: "a2a_task_result",
|
|
197
|
+
threadId: "a2a",
|
|
198
|
+
threadLabel: instruction.slice(0, 80),
|
|
199
|
+
title: task.status === "completed"
|
|
200
|
+
? `A2A ✅: ${instruction.slice(0, 60)}`
|
|
201
|
+
: `A2A ❌: ${instruction.slice(0, 60)}`,
|
|
202
|
+
summary: task.status === "completed"
|
|
203
|
+
? (task.artifacts?.[0]?.parts?.[0]?.text || "").slice(0, 160)
|
|
204
|
+
: task.statusMessage || "Failed",
|
|
205
|
+
instruction,
|
|
206
|
+
messageText: task.status === "completed"
|
|
207
|
+
? (task.artifacts?.[0]?.parts?.[0]?.text || "").slice(0, 500)
|
|
208
|
+
: task.statusMessage || "Failed",
|
|
209
|
+
taskStatus: task.status,
|
|
210
|
+
createdAtMs: task.updatedAtMs || Date.now(),
|
|
211
|
+
readOnly: true,
|
|
212
|
+
provider: "a2a",
|
|
213
|
+
};
|
|
130
214
|
try {
|
|
131
|
-
recordTimelineEntry({
|
|
132
|
-
|
|
133
|
-
runtime,
|
|
134
|
-
|
|
135
|
-
entry: {
|
|
136
|
-
stableId: `a2a_task_result:${task.id}`,
|
|
137
|
-
token: task.token,
|
|
138
|
-
kind: "a2a_task_result",
|
|
139
|
-
threadId: "a2a",
|
|
140
|
-
threadLabel: instruction.slice(0, 80),
|
|
141
|
-
title: task.status === "completed"
|
|
142
|
-
? `A2A ✅: ${instruction.slice(0, 60)}`
|
|
143
|
-
: `A2A ❌: ${instruction.slice(0, 60)}`,
|
|
144
|
-
summary: task.status === "completed"
|
|
145
|
-
? (task.artifacts?.[0]?.parts?.[0]?.text || "").slice(0, 160)
|
|
146
|
-
: task.statusMessage || "Failed",
|
|
147
|
-
instruction,
|
|
148
|
-
messageText: task.status === "completed"
|
|
149
|
-
? (task.artifacts?.[0]?.parts?.[0]?.text || "").slice(0, 500)
|
|
150
|
-
: task.statusMessage || "Failed",
|
|
151
|
-
taskStatus: task.status,
|
|
152
|
-
createdAtMs: task.updatedAtMs || Date.now(),
|
|
153
|
-
readOnly: true,
|
|
154
|
-
provider: "a2a",
|
|
155
|
-
},
|
|
156
|
-
});
|
|
215
|
+
recordTimelineEntry({ config, runtime, state, entry: resultEntry });
|
|
216
|
+
if (typeof recordHistoryItem === "function") {
|
|
217
|
+
recordHistoryItem({ config, runtime, state, item: resultEntry });
|
|
218
|
+
}
|
|
157
219
|
await saveState(config.stateFile, state);
|
|
158
220
|
} catch (error) {
|
|
159
221
|
console.error(`[a2a-exec-timeline] ${error.message}`);
|
|
160
222
|
}
|
|
223
|
+
|
|
224
|
+
// Send web push notification for completion/failure.
|
|
225
|
+
if (typeof deliverWebPushItem === "function") {
|
|
226
|
+
try {
|
|
227
|
+
const isCompleted = task.status === "completed";
|
|
228
|
+
const icon = isCompleted ? "✅" : "❌";
|
|
229
|
+
const resultBody = isCompleted
|
|
230
|
+
? (task.artifacts?.[0]?.parts?.[0]?.text || "").slice(0, 160)
|
|
231
|
+
: (task.statusMessage || "Failed").slice(0, 160);
|
|
232
|
+
await deliverWebPushItem({
|
|
233
|
+
config,
|
|
234
|
+
state,
|
|
235
|
+
kind: "a2a_task_result",
|
|
236
|
+
token: task.token,
|
|
237
|
+
stableId: `a2a_task_result:${task.id}`,
|
|
238
|
+
title: `A2A ${icon}: ${instruction.slice(0, 60)}`,
|
|
239
|
+
body: resultBody || instruction.slice(0, 160),
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`[a2a-exec-push] ${error.message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
161
245
|
}
|
|
162
246
|
|
|
163
247
|
/**
|
|
@@ -172,7 +256,11 @@ function runCodexExec(instruction, config) {
|
|
|
172
256
|
const child = spawn(codexBin, args, {
|
|
173
257
|
cwd,
|
|
174
258
|
stdio: ["pipe", "pipe", "pipe"],
|
|
175
|
-
env: {
|
|
259
|
+
env: {
|
|
260
|
+
...process.env,
|
|
261
|
+
PATH: buildAugmentedPath(codexBin),
|
|
262
|
+
CODEX_HOME: config.codexHome || path.join(os.homedir(), ".codex"),
|
|
263
|
+
},
|
|
176
264
|
timeout: EXEC_TIMEOUT_MS,
|
|
177
265
|
});
|
|
178
266
|
|
|
@@ -223,7 +311,7 @@ function runClaudeExec(instruction) {
|
|
|
223
311
|
const child = spawn(claudeBin, args, {
|
|
224
312
|
cwd: os.tmpdir(),
|
|
225
313
|
stdio: ["pipe", "pipe", "pipe"],
|
|
226
|
-
env: { ...process.env },
|
|
314
|
+
env: { ...process.env, PATH: buildAugmentedPath(claudeBin) },
|
|
227
315
|
timeout: EXEC_TIMEOUT_MS,
|
|
228
316
|
});
|
|
229
317
|
|
package/scripts/a2a-handler.mjs
CHANGED
|
@@ -291,6 +291,10 @@ async function handleMessageSend({
|
|
|
291
291
|
|
|
292
292
|
runtime.a2aTasksByToken.set(token, task);
|
|
293
293
|
|
|
294
|
+
// Update received task counter.
|
|
295
|
+
if (!state.a2aTaskStats) state.a2aTaskStats = { received: 0, completed: 0, denied: 0 };
|
|
296
|
+
state.a2aTaskStats.received += 1;
|
|
297
|
+
|
|
294
298
|
// Record timeline entry
|
|
295
299
|
try {
|
|
296
300
|
recordTimelineEntry({
|
|
@@ -270,6 +270,10 @@ async function ingestRelayTask({ relayTask, config, runtime, state, helpers }) {
|
|
|
270
270
|
|
|
271
271
|
runtime.a2aTasksByToken.set(token, task);
|
|
272
272
|
|
|
273
|
+
// Update received task counter.
|
|
274
|
+
if (!state.a2aTaskStats) state.a2aTaskStats = { received: 0, completed: 0, denied: 0 };
|
|
275
|
+
state.a2aTaskStats.received += 1;
|
|
276
|
+
|
|
273
277
|
// Record timeline entry (same as local A2A handler)
|
|
274
278
|
try {
|
|
275
279
|
recordTimelineEntry({
|
package/scripts/moltbook-api.mjs
CHANGED
|
@@ -172,14 +172,14 @@ export function rollScoutDayIfNeeded(state) {
|
|
|
172
172
|
return state;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
export function recordComposeAttempt(state, title, postId) {
|
|
175
|
+
export function recordComposeAttempt(state, title, postId, type = "post") {
|
|
176
176
|
state.composedToday = (state.composedToday || 0) + 1;
|
|
177
177
|
state.lastComposeDay = todayKey();
|
|
178
178
|
if (!Array.isArray(state.recentComposeTitles)) state.recentComposeTitles = [];
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
if (state.recentComposeTitles.length >
|
|
179
|
+
const entry = { title: String(title || ""), type };
|
|
180
|
+
if (postId) entry.postId = String(postId);
|
|
181
|
+
state.recentComposeTitles.unshift(entry);
|
|
182
|
+
if (state.recentComposeTitles.length > 30) state.recentComposeTitles.length = 30;
|
|
183
183
|
return state;
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -11162,6 +11162,15 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11162
11162
|
const requestedExecutor = cleanText(body.executor || "");
|
|
11163
11163
|
resolveA2ATaskDecision(task, { action, instruction });
|
|
11164
11164
|
|
|
11165
|
+
// Update task stats.
|
|
11166
|
+
if (!state.a2aTaskStats) state.a2aTaskStats = { received: 0, completed: 0, denied: 0 };
|
|
11167
|
+
if (action === "approve") {
|
|
11168
|
+
state.a2aTaskStats.completed += 1;
|
|
11169
|
+
} else {
|
|
11170
|
+
state.a2aTaskStats.denied += 1;
|
|
11171
|
+
}
|
|
11172
|
+
saveState(config.stateFile, state).catch(() => {});
|
|
11173
|
+
|
|
11165
11174
|
if (action === "approve") {
|
|
11166
11175
|
// Resolve which executor to use.
|
|
11167
11176
|
const available = runtime.a2aAvailableExecutors || { codex: false, claude: false };
|
|
@@ -11177,7 +11186,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11177
11186
|
(async () => {
|
|
11178
11187
|
try {
|
|
11179
11188
|
const { executeA2ATask } = await import("./a2a-executor.mjs");
|
|
11180
|
-
await executeA2ATask(task, config, runtime, state, { recordTimelineEntry, saveState }, executor);
|
|
11189
|
+
await executeA2ATask(task, config, runtime, state, { recordTimelineEntry, recordHistoryItem, saveState, deliverWebPushItem }, executor);
|
|
11181
11190
|
} catch (error) {
|
|
11182
11191
|
console.error(`[a2a-execute-error] ${error.message}`);
|
|
11183
11192
|
failA2ATask(task, error.message);
|
|
@@ -11454,6 +11463,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11454
11463
|
return writeJson(res, 200, { enabled: false });
|
|
11455
11464
|
}
|
|
11456
11465
|
const relay = getRelayStatus();
|
|
11466
|
+
const stats = state.a2aTaskStats || { received: 0, completed: 0, denied: 0 };
|
|
11457
11467
|
return writeJson(res, 200, {
|
|
11458
11468
|
enabled: true,
|
|
11459
11469
|
connected: relay.polling && relay.lastPollOk,
|
|
@@ -11464,6 +11474,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
11464
11474
|
relayUrl: config.a2aRelayUrl,
|
|
11465
11475
|
apiKeyConfigured: Boolean(config.a2aApiKey),
|
|
11466
11476
|
acceptPublicTasks: config.a2aAcceptPublicTasks === true,
|
|
11477
|
+
taskStats: stats,
|
|
11467
11478
|
});
|
|
11468
11479
|
}
|
|
11469
11480
|
|
|
@@ -14536,7 +14547,7 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
14536
14547
|
console.log(`[moltbook-draft-post] Posted original post (id=${post?.id})`);
|
|
14537
14548
|
|
|
14538
14549
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
14539
|
-
recordComposeAttempt(scoutState, finalTitle, post?.id);
|
|
14550
|
+
recordComposeAttempt(scoutState, finalTitle, post?.id, "post");
|
|
14540
14551
|
await writeScoutState(scoutState);
|
|
14541
14552
|
} else {
|
|
14542
14553
|
const result = await mb(`/posts/${draft.postId}/comments`, {
|
|
@@ -14553,6 +14564,7 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
14553
14564
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
14554
14565
|
scoutState.sentToday += 1;
|
|
14555
14566
|
markPostSeen(scoutState, draft.postId, "published");
|
|
14567
|
+
recordComposeAttempt(scoutState, draft.postTitle || draft.postId, draft.postId, "reply");
|
|
14556
14568
|
await writeScoutState(scoutState);
|
|
14557
14569
|
}
|
|
14558
14570
|
|
package/web/app.css
CHANGED
|
@@ -609,6 +609,71 @@ code {
|
|
|
609
609
|
color: rgba(121, 196, 255, 0.82);
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
.settings-compose-badge {
|
|
613
|
+
display: inline-block;
|
|
614
|
+
align-self: flex-start;
|
|
615
|
+
padding: 0.12rem 0.42rem;
|
|
616
|
+
border-radius: 6px;
|
|
617
|
+
font-size: 0.7rem;
|
|
618
|
+
font-weight: 600;
|
|
619
|
+
letter-spacing: 0.03em;
|
|
620
|
+
line-height: 1.3;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.settings-compose-badge--post {
|
|
624
|
+
background: rgba(67, 160, 255, 0.18);
|
|
625
|
+
color: rgba(121, 196, 255, 0.92);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.settings-compose-badge--reply {
|
|
629
|
+
background: rgba(160, 120, 255, 0.18);
|
|
630
|
+
color: rgba(190, 160, 255, 0.92);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.settings-compose-entry {
|
|
634
|
+
display: flex;
|
|
635
|
+
flex-direction: column;
|
|
636
|
+
gap: 0.32rem;
|
|
637
|
+
padding: 0.72rem 1rem;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.settings-compose-entry + .settings-compose-entry {
|
|
641
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.settings-compose-entry__title {
|
|
645
|
+
font-size: 0.88rem;
|
|
646
|
+
line-height: 1.4;
|
|
647
|
+
color: var(--text);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.settings-compose-entry__title a {
|
|
651
|
+
color: var(--accent, #00d4aa);
|
|
652
|
+
text-decoration: none;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.settings-compose-entry__title a:hover {
|
|
656
|
+
text-decoration: underline;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.settings-compose-more {
|
|
660
|
+
display: block;
|
|
661
|
+
width: 100%;
|
|
662
|
+
padding: 0.68rem 1rem;
|
|
663
|
+
background: transparent;
|
|
664
|
+
border: 0;
|
|
665
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
666
|
+
color: rgba(121, 196, 255, 0.82);
|
|
667
|
+
font-size: 0.83rem;
|
|
668
|
+
text-align: center;
|
|
669
|
+
cursor: pointer;
|
|
670
|
+
-webkit-tap-highlight-color: transparent;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.settings-compose-more:active {
|
|
674
|
+
background: rgba(255, 255, 255, 0.035);
|
|
675
|
+
}
|
|
676
|
+
|
|
612
677
|
.settings-row__icon {
|
|
613
678
|
width: 1.95rem;
|
|
614
679
|
height: 1.95rem;
|
|
@@ -3250,6 +3315,67 @@ button:disabled {
|
|
|
3250
3315
|
font-size: 0.88rem;
|
|
3251
3316
|
}
|
|
3252
3317
|
|
|
3318
|
+
.a2a-executor-picker {
|
|
3319
|
+
display: flex;
|
|
3320
|
+
gap: 0.5rem;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
.a2a-executor-picker__option {
|
|
3324
|
+
flex: 1;
|
|
3325
|
+
display: flex;
|
|
3326
|
+
align-items: center;
|
|
3327
|
+
justify-content: center;
|
|
3328
|
+
gap: 0.5rem;
|
|
3329
|
+
padding: 0.62rem 0.8rem;
|
|
3330
|
+
border: 1px solid rgba(156, 181, 197, 0.16);
|
|
3331
|
+
border-radius: 12px;
|
|
3332
|
+
background: rgba(255, 255, 255, 0.035);
|
|
3333
|
+
cursor: pointer;
|
|
3334
|
+
font-size: 0.9rem;
|
|
3335
|
+
color: var(--text);
|
|
3336
|
+
transition: border-color 160ms ease, background 160ms ease;
|
|
3337
|
+
-webkit-tap-highlight-color: transparent;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
.a2a-executor-picker__option:has(input:checked) {
|
|
3341
|
+
border-color: rgba(121, 196, 255, 0.5);
|
|
3342
|
+
background: rgba(67, 160, 255, 0.12);
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
.a2a-executor-picker__option:active {
|
|
3346
|
+
background: rgba(255, 255, 255, 0.06);
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
.a2a-executor-picker__option input[type="radio"] {
|
|
3350
|
+
appearance: none;
|
|
3351
|
+
-webkit-appearance: none;
|
|
3352
|
+
width: 1.1rem;
|
|
3353
|
+
height: 1.1rem;
|
|
3354
|
+
border: 2px solid rgba(156, 181, 197, 0.32);
|
|
3355
|
+
border-radius: 50%;
|
|
3356
|
+
background: transparent;
|
|
3357
|
+
flex-shrink: 0;
|
|
3358
|
+
position: relative;
|
|
3359
|
+
cursor: pointer;
|
|
3360
|
+
transition: border-color 160ms ease;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
.a2a-executor-picker__option input[type="radio"]:checked {
|
|
3364
|
+
border-color: rgba(121, 196, 255, 0.92);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
.a2a-executor-picker__option input[type="radio"]:checked::after {
|
|
3368
|
+
content: "";
|
|
3369
|
+
position: absolute;
|
|
3370
|
+
top: 50%;
|
|
3371
|
+
left: 50%;
|
|
3372
|
+
transform: translate(-50%, -50%);
|
|
3373
|
+
width: 0.46rem;
|
|
3374
|
+
height: 0.46rem;
|
|
3375
|
+
border-radius: 50%;
|
|
3376
|
+
background: rgba(121, 196, 255, 0.92);
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3253
3379
|
.field input {
|
|
3254
3380
|
width: 100%;
|
|
3255
3381
|
min-height: 3.25rem;
|
package/web/app.js
CHANGED
|
@@ -48,7 +48,9 @@ const state = {
|
|
|
48
48
|
pairNotice: "",
|
|
49
49
|
pushStatus: null,
|
|
50
50
|
moltbookScoutStatus: null,
|
|
51
|
+
moltbookRecentTitlesExpanded: 0,
|
|
51
52
|
a2aRelayStatus: null,
|
|
53
|
+
a2aTaskExecutorPick: "codex",
|
|
52
54
|
pushNotice: "",
|
|
53
55
|
pushError: "",
|
|
54
56
|
deviceNotice: "",
|
|
@@ -3398,16 +3400,33 @@ function renderSettingsMoltbookPage(context) {
|
|
|
3398
3400
|
renderSettingsInfoRow(L("settings.row.moltbookSeenPosts"), String(scout.seenPostCount)),
|
|
3399
3401
|
])}
|
|
3400
3402
|
${batchRows.length ? renderSettingsGroup(L("settings.moltbook.batchTitle"), batchRows) : ""}
|
|
3401
|
-
${
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3403
|
+
${(() => {
|
|
3404
|
+
const titles = Array.isArray(scout.recentComposeTitles) ? scout.recentComposeTitles : [];
|
|
3405
|
+
if (!titles.length) return "";
|
|
3406
|
+
const PAGE_SIZE = 5;
|
|
3407
|
+
const visibleCount = state.moltbookRecentTitlesExpanded || PAGE_SIZE;
|
|
3408
|
+
const visible = titles.slice(0, visibleCount);
|
|
3409
|
+
const hasMore = titles.length > visibleCount;
|
|
3410
|
+
const rows = visible.map((t) => {
|
|
3411
|
+
const title = typeof t === "string" ? t : (t.title || "");
|
|
3412
|
+
const postId = typeof t === "object" ? t.postId : "";
|
|
3413
|
+
const type = typeof t === "object" ? (t.type || "post") : "post";
|
|
3414
|
+
const badge = type === "reply"
|
|
3415
|
+
? `<span class="settings-compose-badge settings-compose-badge--reply">${escapeHtml(L("settings.moltbook.typeReply"))}</span>`
|
|
3416
|
+
: `<span class="settings-compose-badge settings-compose-badge--post">${escapeHtml(L("settings.moltbook.typePost"))}</span>`;
|
|
3417
|
+
const link = postId
|
|
3418
|
+
? `<a href="https://www.moltbook.com/post/${escapeHtml(postId)}" target="_blank" rel="noopener">${escapeHtml(title)}</a>`
|
|
3419
|
+
: escapeHtml(title);
|
|
3420
|
+
return `<div class="settings-compose-entry">${badge}<span class="settings-compose-entry__title">${link}</span></div>`;
|
|
3421
|
+
});
|
|
3422
|
+
if (hasMore) {
|
|
3423
|
+
const remaining = titles.length - visibleCount;
|
|
3424
|
+
rows.push(`<button type="button" class="settings-compose-more" data-moltbook-titles-more>${escapeHtml(L("settings.moltbook.showMore", { count: remaining }))}</button>`);
|
|
3425
|
+
} else if (titles.length > PAGE_SIZE) {
|
|
3426
|
+
rows.push(`<button type="button" class="settings-compose-more" data-moltbook-titles-collapse>${escapeHtml(L("settings.moltbook.showLess"))}</button>`);
|
|
3427
|
+
}
|
|
3428
|
+
return renderSettingsGroup(L("settings.row.moltbookRecentTitles"), rows);
|
|
3429
|
+
})()}
|
|
3411
3430
|
</div>
|
|
3412
3431
|
`;
|
|
3413
3432
|
}
|
|
@@ -3446,15 +3465,21 @@ function renderSettingsA2aRelayPage(context) {
|
|
|
3446
3465
|
|
|
3447
3466
|
return `
|
|
3448
3467
|
<div class="settings-page">
|
|
3449
|
-
${
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3468
|
+
${(() => {
|
|
3469
|
+
const stats = relay.taskStats || { received: 0, completed: 0, denied: 0 };
|
|
3470
|
+
return renderSettingsGroup("", [
|
|
3471
|
+
renderSettingsInfoRow(L("settings.row.a2aStatus"), statusLabel),
|
|
3472
|
+
renderSettingsInfoRow(L("settings.row.a2aUserId"), userIdLink, { rawValue: true }),
|
|
3473
|
+
renderSettingsInfoRow(L("settings.row.a2aRelay"), relayHost),
|
|
3474
|
+
renderSettingsInfoRow(L("settings.row.a2aApiKey"), relay.apiKeyConfigured ? L("settings.a2aRelay.apiKey.configured") : L("settings.a2aRelay.apiKey.notConfigured")),
|
|
3475
|
+
relay.lastPollAtMs
|
|
3476
|
+
? renderSettingsInfoRow(L("settings.row.a2aLastPoll"), new Date(relay.lastPollAtMs).toLocaleString(state.locale))
|
|
3477
|
+
: "",
|
|
3478
|
+
renderSettingsInfoRow(L("settings.row.a2aTaskReceived"), String(stats.received)),
|
|
3479
|
+
renderSettingsInfoRow(L("settings.row.a2aTaskCompleted"), String(stats.completed)),
|
|
3480
|
+
renderSettingsInfoRow(L("settings.row.a2aTaskDenied"), String(stats.denied)),
|
|
3481
|
+
].filter(Boolean));
|
|
3482
|
+
})()}
|
|
3458
3483
|
<section class="settings-group">
|
|
3459
3484
|
<p class="settings-group__title">${escapeHtml(L("settings.a2aRelay.publicTasks.title"))}</p>
|
|
3460
3485
|
<label class="reply-mode-switch reply-mode-switch--settings" data-a2a-public-toggle>
|
|
@@ -4224,17 +4249,18 @@ function renderA2ATaskDetail(detail, options = {}) {
|
|
|
4224
4249
|
const executors = state.session?.a2aExecutors || { codex: false, claude: false };
|
|
4225
4250
|
const executorPref = state.session?.a2aExecutorPreference || "auto";
|
|
4226
4251
|
const showExecutorPicker = enabled && executorPref === "ask" && executors.codex && executors.claude;
|
|
4252
|
+
const pickedExecutor = state.a2aTaskExecutorPick || "codex";
|
|
4227
4253
|
const executorPicker = showExecutorPicker
|
|
4228
4254
|
? `
|
|
4229
4255
|
<div class="reply-composer__instruction">
|
|
4230
4256
|
<label class="field-label">${escapeHtml(L("a2a.task.executor"))}</label>
|
|
4231
4257
|
<div class="a2a-executor-picker">
|
|
4232
4258
|
<label class="a2a-executor-picker__option">
|
|
4233
|
-
<input type="radio" name="executor" value="codex" checked />
|
|
4259
|
+
<input type="radio" name="executor" value="codex" ${pickedExecutor === "codex" ? "checked" : ""} />
|
|
4234
4260
|
<span>${escapeHtml(L("a2a.executor.codex"))}</span>
|
|
4235
4261
|
</label>
|
|
4236
4262
|
<label class="a2a-executor-picker__option">
|
|
4237
|
-
<input type="radio" name="executor" value="claude" />
|
|
4263
|
+
<input type="radio" name="executor" value="claude" ${pickedExecutor === "claude" ? "checked" : ""} />
|
|
4238
4264
|
<span>${escapeHtml(L("a2a.executor.claude"))}</span>
|
|
4239
4265
|
</label>
|
|
4240
4266
|
</div>
|
|
@@ -5354,6 +5380,19 @@ function bindShellInteractions() {
|
|
|
5354
5380
|
});
|
|
5355
5381
|
}
|
|
5356
5382
|
|
|
5383
|
+
for (const btn of document.querySelectorAll("[data-moltbook-titles-more]")) {
|
|
5384
|
+
btn.addEventListener("click", async () => {
|
|
5385
|
+
state.moltbookRecentTitlesExpanded = (state.moltbookRecentTitlesExpanded || 5) + 5;
|
|
5386
|
+
await renderShell();
|
|
5387
|
+
});
|
|
5388
|
+
}
|
|
5389
|
+
for (const btn of document.querySelectorAll("[data-moltbook-titles-collapse]")) {
|
|
5390
|
+
btn.addEventListener("click", async () => {
|
|
5391
|
+
state.moltbookRecentTitlesExpanded = 0;
|
|
5392
|
+
await renderShell();
|
|
5393
|
+
});
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5357
5396
|
for (const button of document.querySelectorAll("[data-locale-option]")) {
|
|
5358
5397
|
button.addEventListener("click", async () => {
|
|
5359
5398
|
state.pushError = "";
|
|
@@ -5487,6 +5526,12 @@ function bindShellInteractions() {
|
|
|
5487
5526
|
});
|
|
5488
5527
|
}
|
|
5489
5528
|
|
|
5529
|
+
for (const radio of document.querySelectorAll(".a2a-executor-picker input[type='radio']")) {
|
|
5530
|
+
radio.addEventListener("change", () => {
|
|
5531
|
+
if (radio.checked) state.a2aTaskExecutorPick = radio.value;
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5490
5535
|
const a2aTaskForm = document.querySelector("[data-a2a-task-form]");
|
|
5491
5536
|
if (a2aTaskForm) {
|
|
5492
5537
|
const token = a2aTaskForm.dataset.token || "";
|
package/web/i18n.js
CHANGED
|
@@ -371,7 +371,11 @@ const translations = {
|
|
|
371
371
|
"settings.row.moltbookBatchTopScore": "Top score",
|
|
372
372
|
"settings.row.moltbookBatchRemaining": "Time remaining",
|
|
373
373
|
"settings.row.moltbookComposed": "Original posts today",
|
|
374
|
-
"settings.row.moltbookRecentTitles": "Recent
|
|
374
|
+
"settings.row.moltbookRecentTitles": "Recent posts",
|
|
375
|
+
"settings.moltbook.typePost": "Post",
|
|
376
|
+
"settings.moltbook.typeReply": "Reply",
|
|
377
|
+
"settings.moltbook.showMore": "{count} more…",
|
|
378
|
+
"settings.moltbook.showLess": "Show less",
|
|
375
379
|
"moltbook.draft.eyebrowPost": "Moltbook new post",
|
|
376
380
|
"moltbook.draft.eyebrowReply": "Moltbook draft",
|
|
377
381
|
"moltbook.draft.intent": "Intent",
|
|
@@ -400,6 +404,9 @@ const translations = {
|
|
|
400
404
|
"settings.row.a2aRelay": "Relay",
|
|
401
405
|
"settings.row.a2aApiKey": "API Key",
|
|
402
406
|
"settings.row.a2aLastPoll": "Last poll",
|
|
407
|
+
"settings.row.a2aTaskReceived": "Tasks received",
|
|
408
|
+
"settings.row.a2aTaskCompleted": "Tasks completed",
|
|
409
|
+
"settings.row.a2aTaskDenied": "Tasks denied",
|
|
403
410
|
"a2a.task.eyebrow": "A2A Task Request",
|
|
404
411
|
"a2a.task.from": "From",
|
|
405
412
|
"a2a.task.instruction": "Task",
|
|
@@ -1020,7 +1027,11 @@ const translations = {
|
|
|
1020
1027
|
"settings.row.moltbookBatchTopScore": "最高スコア",
|
|
1021
1028
|
"settings.row.moltbookBatchRemaining": "残り時間",
|
|
1022
1029
|
"settings.row.moltbookComposed": "本日の新規投稿数",
|
|
1023
|
-
"settings.row.moltbookRecentTitles": "
|
|
1030
|
+
"settings.row.moltbookRecentTitles": "最近の投稿",
|
|
1031
|
+
"settings.moltbook.typePost": "投稿",
|
|
1032
|
+
"settings.moltbook.typeReply": "返信",
|
|
1033
|
+
"settings.moltbook.showMore": "さらに {count} 件…",
|
|
1034
|
+
"settings.moltbook.showLess": "閉じる",
|
|
1024
1035
|
"moltbook.draft.eyebrowPost": "Moltbook 新規投稿",
|
|
1025
1036
|
"moltbook.draft.eyebrowReply": "Moltbook ドラフト",
|
|
1026
1037
|
"moltbook.draft.intent": "意図",
|
|
@@ -1049,6 +1060,9 @@ const translations = {
|
|
|
1049
1060
|
"settings.row.a2aRelay": "リレー",
|
|
1050
1061
|
"settings.row.a2aApiKey": "API キー",
|
|
1051
1062
|
"settings.row.a2aLastPoll": "最終ポーリング",
|
|
1063
|
+
"settings.row.a2aTaskReceived": "受信タスク",
|
|
1064
|
+
"settings.row.a2aTaskCompleted": "完了タスク",
|
|
1065
|
+
"settings.row.a2aTaskDenied": "拒否タスク",
|
|
1052
1066
|
"a2a.task.eyebrow": "A2A タスクリクエスト",
|
|
1053
1067
|
"a2a.task.from": "送信元",
|
|
1054
1068
|
"a2a.task.instruction": "タスク内容",
|