@myvillage/cli 1.26.0 → 1.30.0
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/src/agent-runtime/loop.js +57 -13
- package/src/agent-runtime/mcp-client.js +17 -9
- package/src/commands/agent-local.js +154 -7
- package/src/index.js +7 -56
- package/src/utils/agent-scaffolder.js +45 -11
- package/src/utils/api.js +0 -49
- package/src/utils/config.js +0 -1
- package/src/utils/formatters.js +0 -180
- package/src/commands/bizreqs.js +0 -941
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Core agent loop using Vercel AI SDK. Reads prompt.md,
|
|
3
3
|
// gathers context, calls LLM with tools, logs results.
|
|
4
4
|
|
|
5
|
-
import { generateText } from 'ai';
|
|
5
|
+
import { generateText, stepCountIs } from 'ai';
|
|
6
6
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
7
7
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
8
8
|
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
@@ -176,7 +176,15 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
176
176
|
const instructionText = activeTask.instruction
|
|
177
177
|
|| (activeTask.input ? JSON.stringify(activeTask.input, null, 2) : '');
|
|
178
178
|
|
|
179
|
-
systemPrompt = `${systemPrompt}\n\n## ACTIVE TASK MODE
|
|
179
|
+
systemPrompt = `${systemPrompt}\n\n## ACTIVE TASK MODE
|
|
180
|
+
A client has assigned you the task below. Your job this iteration is to **execute it fully** using your available tools, not just acknowledge it.
|
|
181
|
+
|
|
182
|
+
Guidelines:
|
|
183
|
+
- Use as many tool calls as needed to complete the task. A single discovery call (e.g. listing directories) is usually NOT enough — follow up with the calls that actually produce the requested result.
|
|
184
|
+
- Do not stop after one tool call unless that call alone fulfills the entire instruction.
|
|
185
|
+
- Do not ask clarifying questions back to the user; make a reasonable interpretation and proceed.
|
|
186
|
+
- The feed context is provided only for situational awareness — do not let "no new feed activity" prevent you from carrying out the task.
|
|
187
|
+
- Your final text response should include the actual answer/output the user asked for (e.g. the list of files, the summary, the result), not just a description of what you did.`;
|
|
180
188
|
|
|
181
189
|
context = `## TASK (id=${activeTask.id}, type=${activeTask.taskType})\n${instructionText}\n\n---\n\n## FEED CONTEXT (for awareness only)\n${context}`;
|
|
182
190
|
|
|
@@ -193,13 +201,20 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
193
201
|
});
|
|
194
202
|
|
|
195
203
|
// Call LLM with tools
|
|
204
|
+
// AI SDK v6 removed `maxSteps` — the loop now uses `stopWhen` with
|
|
205
|
+
// a StopCondition predicate. The default is `stepCountIs(1)`, so
|
|
206
|
+
// without this the model gets exactly ONE round of tool calls and
|
|
207
|
+
// returns immediately. We give it up to 10 steps so a task like
|
|
208
|
+
// "find any README under the workspace" can do
|
|
209
|
+
// list_allowed_directories → list_directory → search_files → ...
|
|
210
|
+
// until it's actually done.
|
|
196
211
|
const result = await generateText({
|
|
197
212
|
model,
|
|
198
213
|
system: systemPrompt,
|
|
199
214
|
prompt: context,
|
|
200
215
|
tools,
|
|
201
|
-
|
|
202
|
-
maxTokens,
|
|
216
|
+
stopWhen: stepCountIs(10),
|
|
217
|
+
maxOutputTokens: maxTokens,
|
|
203
218
|
});
|
|
204
219
|
|
|
205
220
|
// Log LLM response
|
|
@@ -215,6 +230,10 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
215
230
|
|
|
216
231
|
// Log tool calls and count activity. Also audit action-tool success
|
|
217
232
|
// so we don't trust the model's final text about whether a task worked.
|
|
233
|
+
// `toolCallSummary` accumulates a structured per-call record that we
|
|
234
|
+
// attach to the task output so the Activity panel can show what the
|
|
235
|
+
// agent actually did — not just a tool-call count.
|
|
236
|
+
const toolCallSummary = [];
|
|
218
237
|
if (result.steps?.length) {
|
|
219
238
|
for (const step of result.steps) {
|
|
220
239
|
if (step.toolCalls?.length) {
|
|
@@ -228,25 +247,39 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
228
247
|
if (step.toolCalls?.length && step.toolResults?.length) {
|
|
229
248
|
for (let i = 0; i < step.toolResults.length; i++) {
|
|
230
249
|
const tr = step.toolResults[i];
|
|
231
|
-
|
|
250
|
+
// AI SDK v6 renamed `args` → `input`. Try v6 first.
|
|
251
|
+
const args = step.toolCalls[i]?.input ?? step.toolCalls[i]?.args;
|
|
232
252
|
const errored = isToolResultError(tr);
|
|
253
|
+
const resultSummary = summarizeToolResult(tr);
|
|
233
254
|
auditToolCall(taskActionAudit, tr.toolName, errored, tr);
|
|
255
|
+
toolCallSummary.push({
|
|
256
|
+
tool: tr.toolName,
|
|
257
|
+
args,
|
|
258
|
+
result: resultSummary,
|
|
259
|
+
ok: !errored,
|
|
260
|
+
});
|
|
234
261
|
logActivity(agentDir, {
|
|
235
262
|
type: 'tool_call',
|
|
236
263
|
tool: tr.toolName,
|
|
237
264
|
args,
|
|
238
|
-
result:
|
|
265
|
+
result: resultSummary,
|
|
239
266
|
ok: !errored,
|
|
240
267
|
});
|
|
241
268
|
}
|
|
242
269
|
} else if (step.toolResults?.length) {
|
|
243
270
|
for (const tr of step.toolResults) {
|
|
244
271
|
const errored = isToolResultError(tr);
|
|
272
|
+
const resultSummary = summarizeToolResult(tr);
|
|
245
273
|
auditToolCall(taskActionAudit, tr.toolName, errored, tr);
|
|
274
|
+
toolCallSummary.push({
|
|
275
|
+
tool: tr.toolName,
|
|
276
|
+
result: resultSummary,
|
|
277
|
+
ok: !errored,
|
|
278
|
+
});
|
|
246
279
|
logActivity(agentDir, {
|
|
247
280
|
type: 'tool_call',
|
|
248
281
|
tool: tr.toolName,
|
|
249
|
-
result:
|
|
282
|
+
result: resultSummary,
|
|
250
283
|
ok: !errored,
|
|
251
284
|
});
|
|
252
285
|
}
|
|
@@ -259,10 +292,16 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
259
292
|
if (tc.toolName === 'comment_create') activity.commentsCreated++;
|
|
260
293
|
if (tc.toolName === 'vote_cast') activity.votesGiven++;
|
|
261
294
|
// No paired result here — assume executed, can't audit.
|
|
295
|
+
const args = tc.input ?? tc.args;
|
|
296
|
+
toolCallSummary.push({
|
|
297
|
+
tool: tc.toolName,
|
|
298
|
+
args,
|
|
299
|
+
result: 'executed',
|
|
300
|
+
});
|
|
262
301
|
logActivity(agentDir, {
|
|
263
302
|
type: 'tool_call',
|
|
264
303
|
tool: tc.toolName,
|
|
265
|
-
args
|
|
304
|
+
args,
|
|
266
305
|
result: 'executed',
|
|
267
306
|
});
|
|
268
307
|
}
|
|
@@ -281,12 +320,12 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
281
320
|
if (result.steps?.length) {
|
|
282
321
|
for (const step of result.steps) {
|
|
283
322
|
for (const tc of step.toolCalls || []) {
|
|
284
|
-
collectActions(tc.toolName, tc.args);
|
|
323
|
+
collectActions(tc.toolName, tc.input ?? tc.args);
|
|
285
324
|
}
|
|
286
325
|
}
|
|
287
326
|
} else if (result.toolCalls?.length) {
|
|
288
327
|
for (const tc of result.toolCalls) {
|
|
289
|
-
collectActions(tc.toolName, tc.args);
|
|
328
|
+
collectActions(tc.toolName, tc.input ?? tc.args);
|
|
290
329
|
}
|
|
291
330
|
}
|
|
292
331
|
// Keep only last 50 actions to bound memory
|
|
@@ -310,7 +349,8 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
310
349
|
errorMessage,
|
|
311
350
|
output: {
|
|
312
351
|
text: result.text || '',
|
|
313
|
-
|
|
352
|
+
toolCallCount: activity.toolCalls,
|
|
353
|
+
toolCalls: toolCallSummary,
|
|
314
354
|
toolErrors: taskActionAudit.toolErrors,
|
|
315
355
|
note: 'Marked FAILED because the action tools did not succeed. The model\'s text may claim success but the underlying tool calls errored.',
|
|
316
356
|
},
|
|
@@ -326,7 +366,8 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
326
366
|
await completeAgentTask(config.man.village_agent_id, activeTask.id, {
|
|
327
367
|
output: {
|
|
328
368
|
text: result.text || '',
|
|
329
|
-
|
|
369
|
+
toolCallCount: activity.toolCalls,
|
|
370
|
+
toolCalls: toolCallSummary,
|
|
330
371
|
toolErrors: taskActionAudit.toolErrors.length > 0 ? taskActionAudit.toolErrors : undefined,
|
|
331
372
|
},
|
|
332
373
|
tokensUsed: result.usage?.totalTokens || 0,
|
|
@@ -452,7 +493,10 @@ const ACTION_TOOLS = new Set([
|
|
|
452
493
|
|
|
453
494
|
function flattenToolResultText(tr) {
|
|
454
495
|
if (!tr) return '';
|
|
455
|
-
|
|
496
|
+
// AI SDK v6 renamed `result` → `output`. Fall back to the old name so
|
|
497
|
+
// older SDK versions still work if anyone pins them.
|
|
498
|
+
const r = tr.output ?? tr.result;
|
|
499
|
+
if (r == null) return '';
|
|
456
500
|
if (typeof r === 'string') return r;
|
|
457
501
|
if (Array.isArray(r?.content)) {
|
|
458
502
|
return r.content
|
|
@@ -102,15 +102,23 @@ export async function getMCPTools(agentDir, agentConfig) {
|
|
|
102
102
|
},
|
|
103
103
|
}), `${name} (${transport} ${server.url})`);
|
|
104
104
|
} else {
|
|
105
|
-
// Local MCP servers via stdio transport
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
// Local MCP servers via stdio transport.
|
|
106
|
+
//
|
|
107
|
+
// @ai-sdk/mcp v1 doesn't accept a `{ type: 'stdio', ... }` config —
|
|
108
|
+
// its MCPTransportConfig is HTTP-only (`'sse' | 'http'`). For stdio
|
|
109
|
+
// you must instantiate the transport class explicitly from the
|
|
110
|
+
// `/mcp-stdio` subpath and pass the instance.
|
|
111
|
+
const { Experimental_StdioMCPTransport } =
|
|
112
|
+
await import('@ai-sdk/mcp/mcp-stdio');
|
|
113
|
+
const stdioTransport = new Experimental_StdioMCPTransport({
|
|
114
|
+
command: server.command,
|
|
115
|
+
args: server.args || [],
|
|
116
|
+
env: { ...process.env, ...resolveEnvVars(server.env || {}) },
|
|
117
|
+
});
|
|
118
|
+
client = await withTimeout(
|
|
119
|
+
createMCPClient({ transport: stdioTransport }),
|
|
120
|
+
`${name} (stdio ${server.command})`,
|
|
121
|
+
);
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
activeClients.push(client);
|
|
@@ -4,6 +4,7 @@ import { villageSpinner, brand } from '../utils/brand.js';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { existsSync, readFileSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
7
8
|
import { execSync } from 'child_process';
|
|
8
9
|
import { isAuthenticated, getAccessToken } from '../utils/auth.js';
|
|
9
10
|
import { getConfig, setConfig } from '../utils/config.js';
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
agentJoinCommunity as apiAgentJoinCommunity,
|
|
13
14
|
listCommunities,
|
|
14
15
|
getAgentActivity,
|
|
16
|
+
listMyAgents,
|
|
15
17
|
listMyUnlinkedAgentProfiles,
|
|
16
18
|
createVillageAgent as apiCreateVillageAgent,
|
|
17
19
|
listAgentTasks,
|
|
@@ -41,7 +43,7 @@ import {
|
|
|
41
43
|
readAgentLogs,
|
|
42
44
|
getLogFilePath,
|
|
43
45
|
} from '../utils/local-agent.js';
|
|
44
|
-
import { scaffoldAgent, TOOL_CATALOG } from '../utils/agent-scaffolder.js';
|
|
46
|
+
import { scaffoldAgent, TOOL_CATALOG, resolveCatalogEntry } from '../utils/agent-scaffolder.js';
|
|
45
47
|
import { formatLocalAgentList, formatLocalAgentStatus } from '../utils/formatters.js';
|
|
46
48
|
|
|
47
49
|
// ── Create Local Agent (wizard) ─────────────────────────
|
|
@@ -117,11 +119,11 @@ export async function agentCreateLocalCommand() {
|
|
|
117
119
|
message: 'Which tools should your agent have?',
|
|
118
120
|
choices: [
|
|
119
121
|
{ name: 'MyVillageOS MCP (feed, posts, communities, wallet)', value: 'myvillage', checked: true, disabled: 'always enabled' },
|
|
120
|
-
{ name: 'Local Files (read/write
|
|
122
|
+
{ name: 'Local Files (read/write a sandboxed workspace)', value: 'filesystem', checked: true },
|
|
121
123
|
{ name: 'Gmail', value: 'gmail' },
|
|
122
124
|
{ name: 'Calendar', value: 'calendar' },
|
|
123
125
|
{ name: 'GitHub', value: 'github' },
|
|
124
|
-
{ name: 'Browser', value: 'browser' },
|
|
126
|
+
{ name: 'Browser (Puppeteer — downloads Chromium on first run)', value: 'browser' },
|
|
125
127
|
],
|
|
126
128
|
},
|
|
127
129
|
{
|
|
@@ -230,6 +232,26 @@ export async function agentStartCommand(name) {
|
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
if (!agentExists(name)) {
|
|
235
|
+
// Check whether the agent exists server-side under this handle. If so,
|
|
236
|
+
// this is a "different machine" situation \u2014 point them at `agent pull`
|
|
237
|
+
// rather than the misleading "not found" dead-end.
|
|
238
|
+
let remoteMatch = null;
|
|
239
|
+
try {
|
|
240
|
+
remoteMatch = await fetchMyAgentByHandle(name);
|
|
241
|
+
} catch {
|
|
242
|
+
// network error \u2014 fall through to the generic message below
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (remoteMatch) {
|
|
246
|
+
console.log(chalk.yellow(
|
|
247
|
+
` Agent "${name}" exists on the server but isn't set up on this machine yet.`,
|
|
248
|
+
));
|
|
249
|
+
console.log(chalk.yellow(
|
|
250
|
+
` Run ${brand.gold(`myvillage agent pull ${name}`)} to scaffold it locally, then start it.\n`,
|
|
251
|
+
));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
console.log(chalk.red(` \u2717 Agent "${name}" not found. Run 'myvillage agent' to see your agents.\n`));
|
|
234
256
|
return;
|
|
235
257
|
}
|
|
@@ -396,7 +418,7 @@ export async function agentStartCommand(name) {
|
|
|
396
418
|
const toolsConfig = readToolsYaml(name);
|
|
397
419
|
if (toolsConfig.servers?.['man-feed'] && !toolsConfig.servers?.['myvillage']) {
|
|
398
420
|
delete toolsConfig.servers['man-feed'];
|
|
399
|
-
toolsConfig.servers = { myvillage:
|
|
421
|
+
toolsConfig.servers = { myvillage: resolveCatalogEntry('myvillage', name), ...toolsConfig.servers };
|
|
400
422
|
writeToolsYaml(name, toolsConfig);
|
|
401
423
|
}
|
|
402
424
|
|
|
@@ -648,9 +670,22 @@ function printLogEntry(entry) {
|
|
|
648
670
|
console.log(` ${' '.repeat(10)} ${brand.teal(`Tokens: ${entry.tokensUsed.prompt || 0}p / ${entry.tokensUsed.completion || 0}c`)}`);
|
|
649
671
|
}
|
|
650
672
|
break;
|
|
651
|
-
case 'tool_call':
|
|
652
|
-
|
|
673
|
+
case 'tool_call': {
|
|
674
|
+
const arrow = entry.ok === false ? chalk.red('→') : brand.teal('→');
|
|
675
|
+
const statusLabel = entry.ok === false ? chalk.red('error') : '';
|
|
676
|
+
console.log(` ${time} ${chalk.yellow('TOOL')} ${entry.tool || '?'} ${arrow} ${statusLabel}`);
|
|
677
|
+
// Show args (if any) and the result content on indented sub-lines,
|
|
678
|
+
// so a developer tailing logs can actually see what the agent did.
|
|
679
|
+
if (entry.args && Object.keys(entry.args).length > 0) {
|
|
680
|
+
const argsStr = truncateLog(JSON.stringify(entry.args), 200);
|
|
681
|
+
console.log(` ${' '.repeat(10)} ${brand.teal('args:')} ${argsStr}`);
|
|
682
|
+
}
|
|
683
|
+
if (entry.result && entry.result !== 'ok' && entry.result !== 'executed') {
|
|
684
|
+
const resultStr = truncateLog(String(entry.result), 200);
|
|
685
|
+
console.log(` ${' '.repeat(10)} ${brand.teal('result:')} ${resultStr}`);
|
|
686
|
+
}
|
|
653
687
|
break;
|
|
688
|
+
}
|
|
654
689
|
case 'error':
|
|
655
690
|
console.log(` ${time} ${chalk.red('ERR')} ${entry.error || entry.message || 'Unknown error'}`);
|
|
656
691
|
break;
|
|
@@ -710,11 +745,15 @@ export async function agentAddToolCommand(name, tool) {
|
|
|
710
745
|
}
|
|
711
746
|
|
|
712
747
|
toolsConfig.servers = toolsConfig.servers || {};
|
|
713
|
-
toolsConfig.servers[tool] =
|
|
748
|
+
toolsConfig.servers[tool] = resolveCatalogEntry(tool, name);
|
|
714
749
|
writeToolsYaml(name, toolsConfig);
|
|
715
750
|
|
|
716
751
|
console.log(brand.green(` \u2713 Added "${tool}" to agent "${name}".`));
|
|
717
752
|
|
|
753
|
+
if (tool === 'filesystem') {
|
|
754
|
+
const workspace = join(homedir(), '.myvillage', 'agents', name, 'workspace');
|
|
755
|
+
console.log(brand.teal(` Workspace: ${workspace}`));
|
|
756
|
+
}
|
|
718
757
|
if (tool === 'github') {
|
|
719
758
|
console.log(brand.teal(' Note: Set GITHUB_TOKEN in your environment for GitHub access.'));
|
|
720
759
|
}
|
|
@@ -1091,3 +1130,111 @@ export async function agentRememberCommand(name, text, options = {}) {
|
|
|
1091
1130
|
console.log(chalk.red(` ✗ Remember failed: ${msg}\n`));
|
|
1092
1131
|
}
|
|
1093
1132
|
}
|
|
1133
|
+
|
|
1134
|
+
// ── Pull Agent ─────────────────────────────────────────
|
|
1135
|
+
//
|
|
1136
|
+
// Rehydrate a local agent directory on a new machine from the server's
|
|
1137
|
+
// AgentProfile + VillageAgent records. Use case: you created `teacher_mvp`
|
|
1138
|
+
// on your laptop, now you want to run it on your desktop. The server
|
|
1139
|
+
// already has the agent — this command scaffolds the missing local config
|
|
1140
|
+
// (~/.myvillage/agents/<handle>/) so `agent start` can launch the daemon.
|
|
1141
|
+
//
|
|
1142
|
+
// What pulls down: the network identity (id, handle, displayName) and a
|
|
1143
|
+
// link to the existing VillageAgent task queue.
|
|
1144
|
+
// What doesn't: custom prompt edits, tools.yaml customizations, and any
|
|
1145
|
+
// API keys. Those stay machine-local and you'll need to redo them if you
|
|
1146
|
+
// want an exact replica. The scaffold defaults are a reasonable starting
|
|
1147
|
+
// point that will boot.
|
|
1148
|
+
|
|
1149
|
+
async function fetchMyAgentByHandle(handle) {
|
|
1150
|
+
const result = await listMyAgents({ includeVillageAgent: true });
|
|
1151
|
+
const agents = result.data || result;
|
|
1152
|
+
if (!Array.isArray(agents)) return null;
|
|
1153
|
+
return agents.find(a => a.handle === handle) || null;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
export async function agentPullCommand(handle, options = {}) {
|
|
1157
|
+
if (!isAuthenticated()) {
|
|
1158
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// The local agent dir is keyed off the handle (= server-side AgentProfile.handle).
|
|
1163
|
+
const agentName = handle;
|
|
1164
|
+
const force = options.force === true;
|
|
1165
|
+
|
|
1166
|
+
if (agentExists(agentName) && !force) {
|
|
1167
|
+
console.log(chalk.yellow(
|
|
1168
|
+
` Agent "${agentName}" already exists locally at ~/.myvillage/agents/${agentName}/`,
|
|
1169
|
+
));
|
|
1170
|
+
console.log(chalk.yellow(' Use --force to overwrite, or just `myvillage agent start ' + agentName + '` to run it.\n'));
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const spinner = villageSpinner(`Pulling ${handle} from the network...`).start();
|
|
1175
|
+
|
|
1176
|
+
let profile;
|
|
1177
|
+
try {
|
|
1178
|
+
profile = await fetchMyAgentByHandle(handle);
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
spinner.fail(`Failed to look up agent: ${err.response?.data?.error || err.message}`);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (!profile) {
|
|
1185
|
+
spinner.fail(`No agent with handle "${handle}" is owned by your account.`);
|
|
1186
|
+
console.log(chalk.yellow(
|
|
1187
|
+
' Run `myvillage agent` to see the agents on your account, then pull one by its handle.\n',
|
|
1188
|
+
));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Default scaffold: just the always-on MyVillage MCP tool. Users can add
|
|
1193
|
+
// more via `agent add-tool` once they're up.
|
|
1194
|
+
const agentDir = getAgentDir(agentName);
|
|
1195
|
+
try {
|
|
1196
|
+
scaffoldAgent(agentDir, {
|
|
1197
|
+
name: agentName,
|
|
1198
|
+
displayName: profile.displayName || agentName,
|
|
1199
|
+
description: profile.bio || `Pulled from the network on ${new Date().toISOString().split('T')[0]}.`,
|
|
1200
|
+
tools: ['myvillage'],
|
|
1201
|
+
checkInInterval: 'hourly',
|
|
1202
|
+
provider: 'anthropic',
|
|
1203
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
1204
|
+
});
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
spinner.fail(`Failed to scaffold local agent: ${err.message}`);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Prefill man.agent_id and man.village_agent_id so `start` skips the
|
|
1211
|
+
// create-on-first-boot step and links straight to the existing server rows.
|
|
1212
|
+
const cfg = readAgentConfig(agentName);
|
|
1213
|
+
cfg.man = cfg.man || {};
|
|
1214
|
+
cfg.man.agent_id = profile.id;
|
|
1215
|
+
cfg.man.handle = profile.handle;
|
|
1216
|
+
if (profile.villageAgent?.id) {
|
|
1217
|
+
cfg.man.village_agent_id = profile.villageAgent.id;
|
|
1218
|
+
}
|
|
1219
|
+
writeAgentConfig(agentName, cfg);
|
|
1220
|
+
|
|
1221
|
+
spinner.succeed(`Pulled ${handle} to ~/.myvillage/agents/${agentName}/`);
|
|
1222
|
+
|
|
1223
|
+
console.log('');
|
|
1224
|
+
console.log(brand.teal(' What pulled down:'));
|
|
1225
|
+
console.log(` - Network identity: @${profile.handle} (${profile.displayName})`);
|
|
1226
|
+
if (profile.villageAgent?.id) {
|
|
1227
|
+
console.log(` - VillageAgent task queue: ${profile.villageAgent.id}`);
|
|
1228
|
+
} else {
|
|
1229
|
+
console.log(` - ${chalk.yellow('No VillageAgent shim found — one will be created on first start.')}`);
|
|
1230
|
+
}
|
|
1231
|
+
console.log('');
|
|
1232
|
+
console.log(brand.teal(' Defaults that may need tweaking:'));
|
|
1233
|
+
console.log(` - ${brand.gold('prompt.md')} (system prompt — generic template)`);
|
|
1234
|
+
console.log(` - ${brand.gold('tools.yaml')} (only the MyVillage tool is enabled)`);
|
|
1235
|
+
console.log(` - ${brand.gold('agent.config.yaml')} (anthropic + claude-sonnet by default)`);
|
|
1236
|
+
console.log('');
|
|
1237
|
+
console.log(brand.teal(' Next:'));
|
|
1238
|
+
console.log(` ${brand.gold(`myvillage agent edit ${agentName}`)} Customize prompt / tools`);
|
|
1239
|
+
console.log(` ${brand.gold(`myvillage agent start ${agentName}`)} Launch the daemon\n`);
|
|
1240
|
+
}
|
package/src/index.js
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
agentStopCommand,
|
|
56
56
|
agentStatusCommand,
|
|
57
57
|
agentLogsCommand,
|
|
58
|
+
agentPullCommand,
|
|
58
59
|
agentAddToolCommand,
|
|
59
60
|
agentRemoveToolCommand,
|
|
60
61
|
agentTaskListCommand,
|
|
@@ -78,13 +79,6 @@ import {
|
|
|
78
79
|
agentDeactivateClientCommand,
|
|
79
80
|
agentRotateClientKeyCommand,
|
|
80
81
|
} from './commands/agent-client.js';
|
|
81
|
-
import {
|
|
82
|
-
bizreqsNewCommand,
|
|
83
|
-
bizreqsSpecCommand,
|
|
84
|
-
bizreqsListCommand,
|
|
85
|
-
bizreqsStatusCommand,
|
|
86
|
-
bizreqsImportCommand,
|
|
87
|
-
} from './commands/bizreqs.js';
|
|
88
82
|
import {
|
|
89
83
|
gameUpdateCommand,
|
|
90
84
|
gameUploadThumbnailCommand,
|
|
@@ -447,6 +441,12 @@ export function run() {
|
|
|
447
441
|
.description('Start a local agent daemon')
|
|
448
442
|
.action(agentStartCommand);
|
|
449
443
|
|
|
444
|
+
agentCmd
|
|
445
|
+
.command('pull <handle>')
|
|
446
|
+
.description('Rehydrate a local agent dir on a new machine from the server')
|
|
447
|
+
.option('--force', 'Overwrite an existing local agent dir')
|
|
448
|
+
.action(agentPullCommand);
|
|
449
|
+
|
|
450
450
|
agentCmd
|
|
451
451
|
.command('stop <name>')
|
|
452
452
|
.description('Stop a local agent daemon')
|
|
@@ -645,55 +645,6 @@ export function run() {
|
|
|
645
645
|
.description('Show status of a reel draft')
|
|
646
646
|
.action(mediaDraftStatusCommand);
|
|
647
647
|
|
|
648
|
-
// ── BizReqs: Business Requirements Pipeline ───────────
|
|
649
|
-
|
|
650
|
-
const bizreqsCmd = program
|
|
651
|
-
.command('bizreqs')
|
|
652
|
-
.description('Business requirements intake and project pipeline');
|
|
653
|
-
|
|
654
|
-
bizreqsCmd
|
|
655
|
-
.command('new')
|
|
656
|
-
.description('Start a new AI-guided business intake session')
|
|
657
|
-
.option('--org <name>', 'Organization name')
|
|
658
|
-
.option('--contact <name>', 'Contact name')
|
|
659
|
-
.option('--from-file <path>', 'Path to a text file with initial context')
|
|
660
|
-
.option('--from-url <url>', 'URL to the organization\'s website')
|
|
661
|
-
.option('--quick', 'Shortened flow (skip dream state, fewer exchanges)')
|
|
662
|
-
.action(bizreqsNewCommand);
|
|
663
|
-
|
|
664
|
-
bizreqsCmd
|
|
665
|
-
.command('spec <id>')
|
|
666
|
-
.description('Generate full project specification from an intake')
|
|
667
|
-
.option('--detail <level>', 'Detail level: brief, standard, comprehensive', 'standard')
|
|
668
|
-
.option('--output <dir>', 'Output directory for spec file', './specs')
|
|
669
|
-
.action(bizreqsSpecCommand);
|
|
670
|
-
|
|
671
|
-
bizreqsCmd
|
|
672
|
-
.command('list')
|
|
673
|
-
.description('List pipeline submissions')
|
|
674
|
-
.option('--status <status>', 'Filter: new, in-review, spec-ready, assigned, in-progress, delivered')
|
|
675
|
-
.option('--city <city>', 'Filter by city')
|
|
676
|
-
.option('--search <query>', 'Search by org name, solution, or ID')
|
|
677
|
-
.option('--sort <sort>', 'Sort: newest, oldest, priority', 'newest')
|
|
678
|
-
.option('-n, --limit <number>', 'Number of results', '50')
|
|
679
|
-
.option('--offset <number>', 'Pagination offset', '0')
|
|
680
|
-
.option('--json', 'Output raw JSON')
|
|
681
|
-
.action(bizreqsListCommand);
|
|
682
|
-
|
|
683
|
-
bizreqsCmd
|
|
684
|
-
.command('status <id>')
|
|
685
|
-
.description('Check project status')
|
|
686
|
-
.action(bizreqsStatusCommand);
|
|
687
|
-
|
|
688
|
-
bizreqsCmd
|
|
689
|
-
.command('import')
|
|
690
|
-
.description('Import requirements from a file or URL')
|
|
691
|
-
.option('--file <path>', 'Path to .txt or .md file')
|
|
692
|
-
.option('--url <url>', 'URL to fetch content from')
|
|
693
|
-
.option('--org <name>', 'Organization name')
|
|
694
|
-
.option('--contact <name>', 'Contact name')
|
|
695
|
-
.action(bizreqsImportCommand);
|
|
696
|
-
|
|
697
648
|
// ── SoulPrint Studio: Model Training Pipeline ───────────
|
|
698
649
|
|
|
699
650
|
// \u2500\u2500 Wisdom: agent skill packs (Books of Wisdom) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
1
|
+
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
3
4
|
import { stringify as stringifyYaml } from 'yaml';
|
|
4
5
|
|
|
5
6
|
// ── MCP Tool Catalog ────────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// Static-shape entries. The `filesystem` entry's allow-listed root is
|
|
9
|
+
// resolved per-agent at scaffold/add-tool time — see
|
|
10
|
+
// resolveCatalogEntry() — so each agent gets its own sandboxed workspace.
|
|
6
11
|
|
|
7
12
|
const TOOL_CATALOG = {
|
|
8
13
|
myvillage: {
|
|
@@ -12,8 +17,10 @@ const TOOL_CATALOG = {
|
|
|
12
17
|
},
|
|
13
18
|
filesystem: {
|
|
14
19
|
command: 'npx',
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
// Last arg is a placeholder; replaced with `<agentDir>/workspace`
|
|
21
|
+
// by resolveCatalogEntry().
|
|
22
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '__AGENT_WORKSPACE__'],
|
|
23
|
+
description: 'Read/write files under the agent\'s sandboxed workspace',
|
|
17
24
|
},
|
|
18
25
|
gmail: {
|
|
19
26
|
command: 'npx',
|
|
@@ -33,13 +40,41 @@ const TOOL_CATALOG = {
|
|
|
33
40
|
},
|
|
34
41
|
browser: {
|
|
35
42
|
command: 'npx',
|
|
36
|
-
args: ['-y', '@
|
|
37
|
-
description: 'Navigate web pages, extract content',
|
|
43
|
+
args: ['-y', '@modelcontextprotocol/server-puppeteer'],
|
|
44
|
+
description: 'Navigate web pages, extract content (downloads Chromium on first run)',
|
|
38
45
|
},
|
|
39
46
|
};
|
|
40
47
|
|
|
41
48
|
export { TOOL_CATALOG };
|
|
42
49
|
|
|
50
|
+
// ── Catalog Resolution ──────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns a deep-copied catalog entry with per-agent placeholders
|
|
54
|
+
* resolved (e.g. the filesystem workspace path). Also creates any
|
|
55
|
+
* directories the entry depends on (the workspace dir for filesystem).
|
|
56
|
+
*
|
|
57
|
+
* Use this anywhere you'd otherwise do `{ ...TOOL_CATALOG[toolId] }`
|
|
58
|
+
* to write into a tools.yaml.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveCatalogEntry(toolId, agentName) {
|
|
61
|
+
const entry = TOOL_CATALOG[toolId];
|
|
62
|
+
if (!entry) return null;
|
|
63
|
+
const resolved = JSON.parse(JSON.stringify(entry));
|
|
64
|
+
|
|
65
|
+
if (toolId === 'filesystem') {
|
|
66
|
+
const workspace = join(homedir(), '.myvillage', 'agents', agentName, 'workspace');
|
|
67
|
+
if (!existsSync(workspace)) {
|
|
68
|
+
mkdirSync(workspace, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
resolved.args = resolved.args.map(a =>
|
|
71
|
+
a === '__AGENT_WORKSPACE__' ? workspace : a,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
// ── Check-in Interval Map ───────────────────────────────
|
|
44
79
|
|
|
45
80
|
const INTERVAL_MAP = {
|
|
@@ -73,7 +108,7 @@ export function scaffoldAgent(agentDir, options) {
|
|
|
73
108
|
// Write tools.yaml
|
|
74
109
|
writeFileSync(
|
|
75
110
|
join(agentDir, 'tools.yaml'),
|
|
76
|
-
generateToolsYaml(tools)
|
|
111
|
+
generateToolsYaml(tools, name)
|
|
77
112
|
);
|
|
78
113
|
|
|
79
114
|
// Write logs/.gitkeep
|
|
@@ -156,17 +191,16 @@ When you receive a TASK in your context, follow these rules:
|
|
|
156
191
|
|
|
157
192
|
// ── Tools YAML Generation ───────────────────────────────
|
|
158
193
|
|
|
159
|
-
function generateToolsYaml(selectedTools) {
|
|
194
|
+
function generateToolsYaml(selectedTools, agentName) {
|
|
160
195
|
const servers = {};
|
|
161
196
|
|
|
162
197
|
// myvillage is always included
|
|
163
|
-
servers['myvillage'] =
|
|
198
|
+
servers['myvillage'] = resolveCatalogEntry('myvillage', agentName);
|
|
164
199
|
|
|
165
200
|
for (const toolId of selectedTools) {
|
|
166
201
|
if (toolId === 'myvillage') continue;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
202
|
+
const entry = resolveCatalogEntry(toolId, agentName);
|
|
203
|
+
if (entry) servers[toolId] = entry;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
return stringifyYaml({ servers }, { lineWidth: 0 });
|