@myvillage/cli 1.85.0 → 1.86.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myvillage/cli",
3
- "version": "1.85.0",
3
+ "version": "1.86.0",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -635,6 +635,25 @@ const ACTION_TOOLS = new Set([
635
635
  'task_assign',
636
636
  'task_complete',
637
637
  'task_retry',
638
+ // Curriculum actions — creating/assigning activities and projects is real,
639
+ // externally-visible work. Without these, a curriculum-generator agent that
640
+ // successfully creates and assigns activities scores zero successful action
641
+ // tools, so any incidental flagged result marks the whole task FAILED.
642
+ 'activity_create',
643
+ 'activity_assign',
644
+ 'activity_update',
645
+ 'activity_delete',
646
+ 'project_create',
647
+ 'project_update',
648
+ 'project_delete',
649
+ ]);
650
+
651
+ // Read-only lookups whose "not found" result is an expected empty read, not a
652
+ // failure. A missing agent_memory_get key is normal the first time an agent
653
+ // touches a villager and must not be treated as a task-failing tool error.
654
+ const BENIGN_NOT_FOUND_TOOLS = new Set([
655
+ 'agent_memory_get',
656
+ 'agent_memory_list',
638
657
  ]);
639
658
 
640
659
  function flattenToolResultText(tr) {
@@ -666,6 +685,14 @@ function isToolResultError(tr) {
666
685
  // "the user was unauthorized to do X" because we anchor on word boundaries.
667
686
  const text = flattenToolResultText(tr);
668
687
  if (!text) return false;
688
+ // A memory/lookup read that misses is an expected empty result, not an
689
+ // error. agent_memory_get returns "Memory key ... not found" the first time
690
+ // an agent touches a villager; flagging that would fail the whole task even
691
+ // though nothing went wrong. Real transport errors on these tools still set
692
+ // the explicit isError flag handled above, so they are unaffected.
693
+ if (BENIGN_NOT_FOUND_TOOLS.has(tr.toolName) && /\bnot found\b/i.test(text)) {
694
+ return false;
695
+ }
669
696
  return /\b(40[0-9]|50[0-9])\b/.test(text) ||
670
697
  /\b(not found|unauthorized|forbidden|invalid|insufficient_quota|authentication failed)\b/i.test(text);
671
698
  }
@@ -333,12 +333,19 @@ export async function agentRecipesListCommand() {
333
333
 
334
334
  // ── Start Agent ─────────────────────────────────────────
335
335
 
336
- export async function agentStartCommand(name) {
336
+ export async function agentStartCommand(name, options = {}) {
337
337
  if (!isAuthenticated()) {
338
338
  console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
339
339
  return;
340
340
  }
341
341
 
342
+ // Organizational village tags for this agent (grouping/filtering only \u2014 no
343
+ // access effect). Accept either a pre-parsed array (if a caller passes one)
344
+ // or a comma-separated string from the --village flag.
345
+ const associatedVillages = Array.isArray(options.village)
346
+ ? options.village.map(v => String(v).trim()).filter(Boolean)
347
+ : (options.village || '').split(',').map(v => v.trim()).filter(Boolean);
348
+
342
349
  if (!agentExists(name)) {
343
350
  // Check whether the agent exists server-side under this handle. If so,
344
351
  // this is a "different machine" situation \u2014 point them at `agent pull`
@@ -453,7 +460,7 @@ export async function agentStartCommand(name) {
453
460
  archetypes: [],
454
461
  greeting: '',
455
462
  starterPrompts: [],
456
- associatedVillages: [],
463
+ associatedVillages,
457
464
  specialInstructions: null,
458
465
  agentProfileId: agent.id,
459
466
  });
@@ -512,7 +519,7 @@ export async function agentStartCommand(name) {
512
519
  archetypes: [],
513
520
  greeting: '',
514
521
  starterPrompts: [],
515
- associatedVillages: [],
522
+ associatedVillages,
516
523
  specialInstructions: null,
517
524
  agentProfileId: agentConfig.man.agent_id,
518
525
  });
@@ -27,7 +27,7 @@ async function resolveAgentHandle(handle) {
27
27
 
28
28
  // ── Commands ─────────────────────────────────────────
29
29
 
30
- export async function agentCommand(handle) {
30
+ export async function agentCommand(handle, options = {}) {
31
31
  if (!isAuthenticated()) {
32
32
  console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
33
33
  return;
@@ -46,7 +46,8 @@ export async function agentCommand(handle) {
46
46
  const spinner = villageSpinner('Loading remote agents...').start();
47
47
 
48
48
  try {
49
- const result = await listMyAgents();
49
+ // --village filters remote agents by an associated village id (organizational).
50
+ const result = await listMyAgents(options.village ? { village: options.village } : {});
50
51
  spinner.stop();
51
52
 
52
53
  const agents = result.data || result;
package/src/index.js CHANGED
@@ -440,6 +440,7 @@ export function run() {
440
440
  const agentCmd = program
441
441
  .command('agent [handle]')
442
442
  .description('List your agents, or view an agent by handle')
443
+ .option('--village <id>', 'Filter agents by associated village ID')
443
444
  .action(agentCommand);
444
445
 
445
446
  agentCmd
@@ -510,6 +511,7 @@ export function run() {
510
511
  agentCmd
511
512
  .command('start <name>')
512
513
  .description('Start a local agent daemon')
514
+ .option('--village <ids>', 'Comma-separated village IDs to tag this agent with (organizational only; does not change access)')
513
515
  .action(agentStartCommand);
514
516
 
515
517
  agentCmd
package/src/utils/api.js CHANGED
@@ -290,8 +290,14 @@ export async function createAgent(data) {
290
290
  return response.data;
291
291
  }
292
292
 
293
- export async function listMyAgents(params = {}) {
294
- const client = getNetworkClient();
293
+ export async function listMyAgents(options = {}) {
294
+ const client = getNetworkClient();
295
+ // Pass through recognized query params. `village` filters by an associated
296
+ // village id (organizational grouping only); existing callers may also pass
297
+ // excludeSystem / unlinked etc., so spread the rest through unchanged.
298
+ const { village, ...rest } = options;
299
+ const params = { ...rest };
300
+ if (village) params.village = village;
295
301
  const response = await client.get('/agents/my', { params });
296
302
  return response.data;
297
303
  }
@@ -689,11 +695,24 @@ export async function claimAgentTask(villageAgentId, taskId) {
689
695
  // otherwise the task is marked COMPLETED with the given `output`.
690
696
  export async function completeAgentTask(villageAgentId, taskId, data = {}) {
691
697
  const client = getPlatformClient();
692
- const response = await client.post(
693
- `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/complete`,
694
- data,
695
- );
696
- return response.data;
698
+ try {
699
+ const response = await client.post(
700
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/complete`,
701
+ data,
702
+ );
703
+ return response.data;
704
+ } catch (err) {
705
+ // Idempotent completion. The task may already be terminal because the agent
706
+ // completed it itself via the `agent_task_complete` tool during its run (the
707
+ // daemon also completes the active task at the end of each iteration), or a
708
+ // retry raced. A 409 "already terminal" means the task reached the end state
709
+ // we wanted — treat it as success rather than a failure so the daemon doesn't
710
+ // log a spurious "Failed to mark task complete" error every run.
711
+ if (err?.response?.status === 409) {
712
+ return { alreadyTerminal: true, message: err.response?.data?.error };
713
+ }
714
+ throw err;
715
+ }
697
716
  }
698
717
 
699
718
  // Retry a single FAILED or CANCELLED task: resets it to PENDING so the
@@ -398,6 +398,8 @@ export function formatAgentList(agents) {
398
398
  if (agent.agentCategory && agent.agentCategory !== 'NETWORK' && agent.schedule) {
399
399
  console.log(` ${chalk.dim(' ')}${chalk.dim(`Schedule: ${agent.schedule}`)}`);
400
400
  }
401
+ const villages = Array.isArray(agent.associatedVillages) ? agent.associatedVillages.filter(Boolean) : [];
402
+ console.log(` ${chalk.dim(' ')}${chalk.dim(`Villages: ${villages.length ? villages.join(', ') : '—'}`)}`);
401
403
  console.log('');
402
404
  }
403
405
  }