viveworker 0.4.1 ā 0.4.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/package.json +1 -1
- package/scripts/a2a-cli.mjs +171 -1
- package/scripts/a2a-handler.mjs +3 -1
- package/scripts/viveworker-bridge.mjs +39 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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-cli.mjs
CHANGED
|
@@ -33,8 +33,15 @@ export async function runA2ACli(args) {
|
|
|
33
33
|
switch (cmd) {
|
|
34
34
|
case "setup":
|
|
35
35
|
return handleSetup(args.slice(1));
|
|
36
|
+
case "activity":
|
|
37
|
+
return handleActivity(args.slice(1));
|
|
38
|
+
case "card":
|
|
39
|
+
return handleCard(args.slice(1));
|
|
36
40
|
default:
|
|
37
|
-
console.log("
|
|
41
|
+
console.log("Commands:");
|
|
42
|
+
console.log(" viveworker a2a setup --user-id <id> [--description <text>] [--skills <csv>]");
|
|
43
|
+
console.log(" viveworker a2a activity [--state-file <path>]");
|
|
44
|
+
console.log(" viveworker a2a card [--description <text>] [--skills <csv>] [--avatar <url-or-emoji>]");
|
|
38
45
|
if (cmd && cmd !== "help" && cmd !== "--help") {
|
|
39
46
|
throw new Error(`Unknown command: ${cmd}`);
|
|
40
47
|
}
|
|
@@ -52,6 +59,7 @@ async function handleSetup(args) {
|
|
|
52
59
|
const timeout = Number(flags["timeout"]) || DEFAULT_TIMEOUT;
|
|
53
60
|
const description = flags["description"] || "";
|
|
54
61
|
const skillsRaw = flags["skills"] || "";
|
|
62
|
+
const avatar = flags["avatar"] || "";
|
|
55
63
|
|
|
56
64
|
if (!userId) {
|
|
57
65
|
throw new Error("--user-id is required\nUsage: viveworker a2a setup --user-id <id>");
|
|
@@ -62,6 +70,7 @@ async function handleSetup(args) {
|
|
|
62
70
|
console.log(` User ID: ${userId}`);
|
|
63
71
|
if (description) console.log(` Desc: ${description}`);
|
|
64
72
|
if (skillsRaw) console.log(` Skills: ${skillsRaw}`);
|
|
73
|
+
if (avatar) console.log(` Avatar: ${avatar}`);
|
|
65
74
|
console.log();
|
|
66
75
|
|
|
67
76
|
// Step 1: Create setup session
|
|
@@ -135,6 +144,7 @@ async function handleSetup(args) {
|
|
|
135
144
|
};
|
|
136
145
|
if (description) envVars.A2A_DESCRIPTION = description;
|
|
137
146
|
if (skillsRaw) envVars.A2A_SKILLS = skillsRaw;
|
|
147
|
+
if (avatar) envVars.A2A_AVATAR = avatar;
|
|
138
148
|
|
|
139
149
|
const updated = upsertEnvText(currentEnv, envVars);
|
|
140
150
|
|
|
@@ -146,6 +156,166 @@ async function handleSetup(args) {
|
|
|
146
156
|
console.log(` Your A2A endpoint: ${result.relayUrl}/${result.userId}\n`);
|
|
147
157
|
}
|
|
148
158
|
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// activity command
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
async function handleActivity(args) {
|
|
164
|
+
const flags = parseFlags(args);
|
|
165
|
+
|
|
166
|
+
// Find state file: explicit flag > STATE_FILE env > default locations
|
|
167
|
+
const candidates = [
|
|
168
|
+
flags["state-file"] || flags["stateFile"],
|
|
169
|
+
process.env.STATE_FILE,
|
|
170
|
+
path.join(os.homedir(), ".viveworker", "state.json"),
|
|
171
|
+
path.join(process.cwd(), ".viveworker-state.json"),
|
|
172
|
+
].filter(Boolean);
|
|
173
|
+
|
|
174
|
+
let stateData = null;
|
|
175
|
+
let usedPath = "";
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
try {
|
|
178
|
+
const raw = await fs.readFile(candidate, "utf8");
|
|
179
|
+
stateData = JSON.parse(raw);
|
|
180
|
+
usedPath = candidate;
|
|
181
|
+
break;
|
|
182
|
+
} catch {
|
|
183
|
+
// try next
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!stateData) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"Could not find viveworker state file.\n" +
|
|
190
|
+
"Try: viveworker a2a activity --state-file /path/to/.viveworker-state.json\n" +
|
|
191
|
+
"Or set STATE_FILE environment variable."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const entries = stateData.recentTimelineEntries || [];
|
|
196
|
+
if (entries.length === 0) {
|
|
197
|
+
console.log(JSON.stringify({ totalEntries: 0, providers: {}, threads: [], recentTasks: [] }));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Provider usage
|
|
202
|
+
const providers = {};
|
|
203
|
+
for (const e of entries) {
|
|
204
|
+
if (e.provider) providers[e.provider] = (providers[e.provider] || 0) + 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Thread topics with activity counts
|
|
208
|
+
const threadMap = new Map();
|
|
209
|
+
for (const e of entries) {
|
|
210
|
+
const label = e.threadLabel || e.title;
|
|
211
|
+
if (!label || label === "Moltbook") continue;
|
|
212
|
+
if (!threadMap.has(label)) threadMap.set(label, { count: 0, providers: new Set() });
|
|
213
|
+
const t = threadMap.get(label);
|
|
214
|
+
t.count++;
|
|
215
|
+
if (e.provider) t.providers.add(e.provider);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const threads = [...threadMap.entries()]
|
|
219
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
220
|
+
.map(([label, v]) => ({
|
|
221
|
+
label: label.slice(0, 120),
|
|
222
|
+
count: v.count,
|
|
223
|
+
providers: [...v.providers],
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
// Recent task titles (from assistant_final and user_message kinds)
|
|
227
|
+
const taskKinds = new Set(["assistant_final", "user_message", "completion"]);
|
|
228
|
+
const recentTasks = [];
|
|
229
|
+
const seenTitles = new Set();
|
|
230
|
+
for (let i = entries.length - 1; i >= 0 && recentTasks.length < 15; i--) {
|
|
231
|
+
const e = entries[i];
|
|
232
|
+
if (!taskKinds.has(e.kind)) continue;
|
|
233
|
+
const title = (e.title || e.threadLabel || "").trim();
|
|
234
|
+
if (!title || seenTitles.has(title)) continue;
|
|
235
|
+
seenTitles.add(title);
|
|
236
|
+
recentTasks.push({
|
|
237
|
+
title: title.slice(0, 120),
|
|
238
|
+
provider: e.provider || "",
|
|
239
|
+
kind: e.kind,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const result = {
|
|
244
|
+
stateFile: usedPath,
|
|
245
|
+
totalEntries: entries.length,
|
|
246
|
+
providers,
|
|
247
|
+
threads,
|
|
248
|
+
recentTasks,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
console.log(JSON.stringify(result, null, 2));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// card command
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
async function handleCard(args) {
|
|
259
|
+
const flags = parseFlags(args);
|
|
260
|
+
const description = flags["description"] || "";
|
|
261
|
+
const skillsRaw = flags["skills"] || "";
|
|
262
|
+
const avatar = flags["avatar"] || "";
|
|
263
|
+
|
|
264
|
+
if (!description && !skillsRaw && !avatar) {
|
|
265
|
+
// No flags ā show current values
|
|
266
|
+
let currentEnv = "";
|
|
267
|
+
try {
|
|
268
|
+
currentEnv = await fs.readFile(A2A_ENV_FILE, "utf8");
|
|
269
|
+
} catch {
|
|
270
|
+
throw new Error(`No a2a.env found at ${A2A_ENV_FILE}. Run 'viveworker a2a setup' first.`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const currentDesc = envValue(currentEnv, "A2A_DESCRIPTION");
|
|
274
|
+
const currentSkills = envValue(currentEnv, "A2A_SKILLS");
|
|
275
|
+
const currentAvatar = envValue(currentEnv, "A2A_AVATAR");
|
|
276
|
+
|
|
277
|
+
console.log(`\nš Current Agent Card settings (${A2A_ENV_FILE})\n`);
|
|
278
|
+
console.log(` Description: ${currentDesc || "(not set)"}`);
|
|
279
|
+
console.log(` Skills: ${currentSkills || "(not set)"}`);
|
|
280
|
+
console.log(` Avatar: ${currentAvatar || "(not set ā uses GitHub avatar or š¤)"}\n`);
|
|
281
|
+
console.log(`To update: viveworker a2a card --description "..." --skills "..." --avatar "..."`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Update a2a.env
|
|
286
|
+
let currentEnv = "";
|
|
287
|
+
try {
|
|
288
|
+
currentEnv = await fs.readFile(A2A_ENV_FILE, "utf8");
|
|
289
|
+
} catch {
|
|
290
|
+
throw new Error(`No a2a.env found at ${A2A_ENV_FILE}. Run 'viveworker a2a setup' first.`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const updates = {};
|
|
294
|
+
if (description) updates.A2A_DESCRIPTION = description;
|
|
295
|
+
if (skillsRaw) updates.A2A_SKILLS = skillsRaw;
|
|
296
|
+
if (avatar) updates.A2A_AVATAR = avatar;
|
|
297
|
+
|
|
298
|
+
const updated = upsertEnvText(currentEnv, updates);
|
|
299
|
+
await fs.writeFile(A2A_ENV_FILE, updated, { mode: 0o600 });
|
|
300
|
+
|
|
301
|
+
console.log(`\nā
Agent Card updated in ${A2A_ENV_FILE}\n`);
|
|
302
|
+
if (description) console.log(` Description: ${description}`);
|
|
303
|
+
if (skillsRaw) console.log(` Skills: ${skillsRaw}`);
|
|
304
|
+
if (avatar) console.log(` Avatar: ${avatar}`);
|
|
305
|
+
console.log(`\nš The bridge will pick up the change within 30 seconds and re-register with the relay.\n`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function envValue(text, key) {
|
|
309
|
+
for (const line of text.split(/\r?\n/)) {
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
312
|
+
const eq = trimmed.indexOf("=");
|
|
313
|
+
if (eq === -1) continue;
|
|
314
|
+
if (trimmed.slice(0, eq) === key) return trimmed.slice(eq + 1);
|
|
315
|
+
}
|
|
316
|
+
return "";
|
|
317
|
+
}
|
|
318
|
+
|
|
149
319
|
// ---------------------------------------------------------------------------
|
|
150
320
|
// Helpers
|
|
151
321
|
// ---------------------------------------------------------------------------
|
package/scripts/a2a-handler.mjs
CHANGED
|
@@ -60,7 +60,7 @@ export function buildAgentCard(config) {
|
|
|
60
60
|
];
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
const card = {
|
|
64
64
|
schemaVersion: "1.0",
|
|
65
65
|
humanReadableId: `viveworker/${config.a2aRelayUserId || "viveworker"}`,
|
|
66
66
|
agentVersion: config.version || "0.3.0",
|
|
@@ -76,6 +76,8 @@ export function buildAgentCard(config) {
|
|
|
76
76
|
skills,
|
|
77
77
|
authSchemes: [{ scheme: "apiKey", in: "header", name: "X-A2A-Key" }],
|
|
78
78
|
};
|
|
79
|
+
if (config.a2aAvatar) card.avatar = config.a2aAvatar;
|
|
80
|
+
return card;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
// ---------------------------------------------------------------------------
|
|
@@ -13789,6 +13789,7 @@ function buildConfig(cli) {
|
|
|
13789
13789
|
a2aPublicUrl: cleanText(process.env.A2A_PUBLIC_URL || ""),
|
|
13790
13790
|
a2aDescription: cleanText(process.env.A2A_DESCRIPTION || ""),
|
|
13791
13791
|
a2aSkills: cleanText(process.env.A2A_SKILLS || ""),
|
|
13792
|
+
a2aAvatar: cleanText(process.env.A2A_AVATAR || ""),
|
|
13792
13793
|
a2aRelayUrl: cleanText(process.env.A2A_RELAY_URL || ""),
|
|
13793
13794
|
a2aRelayUserId: cleanText(process.env.A2A_RELAY_USER_ID || ""),
|
|
13794
13795
|
a2aRelaySecret: cleanText(process.env.A2A_RELAY_SECRET || ""),
|
|
@@ -15194,31 +15195,52 @@ async function main() {
|
|
|
15194
15195
|
console.error(`[scan-error] ${error.message}`);
|
|
15195
15196
|
}
|
|
15196
15197
|
|
|
15197
|
-
// Hot-reload: detect a2a.env changes
|
|
15198
|
+
// Hot-reload: detect a2a.env changes (new config or updated card)
|
|
15198
15199
|
const now = Date.now();
|
|
15199
|
-
if (
|
|
15200
|
+
if (now - lastA2aEnvCheckAt > A2A_ENV_CHECK_INTERVAL_MS) {
|
|
15200
15201
|
lastA2aEnvCheckAt = now;
|
|
15201
15202
|
try {
|
|
15202
15203
|
loadEnvFile(path.join(os.homedir(), ".viveworker", "a2a.env"));
|
|
15203
15204
|
const newRelayUrl = cleanText(process.env.A2A_RELAY_URL || "");
|
|
15204
15205
|
const newRelayUserId = cleanText(process.env.A2A_RELAY_USER_ID || "");
|
|
15205
15206
|
const newRelayRegisterSecret = cleanText(process.env.A2A_RELAY_REGISTER_SECRET || "");
|
|
15207
|
+
const newDescription = cleanText(process.env.A2A_DESCRIPTION || "");
|
|
15208
|
+
const newSkills = cleanText(process.env.A2A_SKILLS || "");
|
|
15209
|
+
const newAvatar = cleanText(process.env.A2A_AVATAR || "");
|
|
15210
|
+
|
|
15206
15211
|
if (newRelayUrl && newRelayUserId) {
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
config.
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15212
|
+
// Check if this is a first-time connect or a card update
|
|
15213
|
+
const isNew = !config.a2aRelayUrl;
|
|
15214
|
+
const cardChanged = config.a2aDescription !== newDescription || config.a2aSkills !== newSkills || config.a2aAvatar !== newAvatar;
|
|
15215
|
+
|
|
15216
|
+
if (isNew || cardChanged) {
|
|
15217
|
+
config.a2aRelayUrl = newRelayUrl;
|
|
15218
|
+
config.a2aRelayUserId = newRelayUserId;
|
|
15219
|
+
config.a2aRelayRegisterSecret = newRelayRegisterSecret;
|
|
15220
|
+
config.a2aApiKey = cleanText(process.env.A2A_API_KEY || config.a2aApiKey || "");
|
|
15221
|
+
config.a2aDescription = newDescription;
|
|
15222
|
+
config.a2aSkills = newSkills;
|
|
15223
|
+
config.a2aAvatar = newAvatar;
|
|
15224
|
+
|
|
15225
|
+
if (isNew) {
|
|
15226
|
+
console.log(`[a2a-relay] Detected new relay config, registering...`);
|
|
15227
|
+
} else {
|
|
15228
|
+
console.log(`[a2a-relay] Agent Card changed, re-registering...`);
|
|
15229
|
+
}
|
|
15230
|
+
|
|
15231
|
+
const regResult = await registerWithRelay({ config, buildAgentCard });
|
|
15232
|
+
if (regResult.ok) {
|
|
15233
|
+
if (isNew) {
|
|
15234
|
+
startRelayPolling({
|
|
15235
|
+
config,
|
|
15236
|
+
runtime,
|
|
15237
|
+
state,
|
|
15238
|
+
helpers: { recordTimelineEntry, deliverWebPushItem, saveState, historyToken, cleanText },
|
|
15239
|
+
});
|
|
15240
|
+
}
|
|
15241
|
+
} else {
|
|
15242
|
+
console.error(`[a2a-relay] Registration failed: ${regResult.error}`);
|
|
15243
|
+
}
|
|
15222
15244
|
}
|
|
15223
15245
|
}
|
|
15224
15246
|
} catch (error) {
|