@myvillage/cli 1.31.0 → 1.33.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.31.0",
3
+ "version": "1.33.0",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,25 +2,28 @@
2
2
  // Assembles a context snapshot for each agent loop iteration.
3
3
  // This becomes the user message sent to the LLM.
4
4
 
5
- import { getLatestFeed, getAgentMentions } from '../utils/api.js';
5
+ import { getAgentFeed, getAgentMentions, getAgentEvents } from '../utils/api.js';
6
6
 
7
7
  export async function gatherContext(agentConfig, lastCheckIn, recentActions = []) {
8
8
  const parts = [];
9
9
  let mentionsCount = 0;
10
+ let eventsCount = 0;
10
11
 
11
12
  parts.push(`Current time: ${new Date().toISOString()}`);
12
13
  parts.push(`Last check-in: ${lastCheckIn || 'never'}`);
13
14
 
14
15
  const agentId = agentConfig.man?.agent_id;
15
16
 
16
- // Fetch new feed activity since last check-in
17
+ // Fetch new feed activity since last check-in. The agent-scoped feed
18
+ // endpoint is enforced server-side: posts from public communities OR
19
+ // communities this agent is a member of. Caller cannot widen the scope.
17
20
  if (agentId) {
18
21
  try {
19
22
  const params = { pageSize: 30 };
20
23
  if (lastCheckIn) {
21
24
  params.since = lastCheckIn;
22
25
  }
23
- const result = await getLatestFeed(params);
26
+ const result = await getAgentFeed(agentId, params);
24
27
  const items = result.data || result;
25
28
 
26
29
  if (Array.isArray(items) && items.length > 0) {
@@ -43,6 +46,27 @@ export async function gatherContext(agentConfig, lastCheckIn, recentActions = []
43
46
  parts.push('\nCould not fetch feed (network error).');
44
47
  }
45
48
 
49
+ // Fetch network events (NEW_POST, NEW_MEMBER, etc.) the agent has visibility for.
50
+ try {
51
+ const eventParams = { pageSize: 20 };
52
+ if (lastCheckIn) eventParams.since = lastCheckIn;
53
+ const eventResult = await getAgentEvents(agentId, eventParams);
54
+ const events = eventResult.data || eventResult;
55
+
56
+ if (Array.isArray(events) && events.length > 0) {
57
+ eventsCount = events.length;
58
+ parts.push(`\nNetwork events since last check-in (${events.length}):`);
59
+ for (const e of events.slice(0, 10)) {
60
+ parts.push(`- [${e.eventType}] source=${e.sourceEntityType}:${e.sourceEntityId}`);
61
+ }
62
+ if (events.length > 10) {
63
+ parts.push(`... and ${events.length - 10} more events`);
64
+ }
65
+ }
66
+ } catch {
67
+ // Events fetch failed — not critical, skip silently
68
+ }
69
+
46
70
  // Fetch mentions since last check-in
47
71
  try {
48
72
  const mentionParams = { pageSize: 20 };
@@ -95,5 +119,6 @@ export async function gatherContext(agentConfig, lastCheckIn, recentActions = []
95
119
  return {
96
120
  text: parts.join('\n'),
97
121
  mentionsCount,
122
+ eventsCount,
98
123
  };
99
124
  }
@@ -186,10 +186,11 @@ export async function agentLoop(agentName, { signal }) {
186
186
  activeTask = await pollAndClaim(config.man.village_agent_id, agentDir);
187
187
  }
188
188
 
189
- // Gather context (returns { text, mentionsCount })
189
+ // Gather context (returns { text, mentionsCount, eventsCount })
190
190
  const contextResult = await gatherContext(config, lastCheckIn, recentActions);
191
191
  let context = contextResult.text;
192
192
  mentionsFound = contextResult.mentionsCount;
193
+ const eventsFound = contextResult.eventsCount || 0;
193
194
 
194
195
  if (activeTask) {
195
196
  // A claimed task takes over the iteration. The default monitoring
@@ -222,6 +223,7 @@ Guidelines:
222
223
  type: 'context',
223
224
  feedItems: feedItemsRead,
224
225
  mentions: mentionsFound,
226
+ events: eventsFound,
225
227
  });
226
228
 
227
229
  // Call LLM with tools
@@ -431,11 +433,17 @@ Guidelines:
431
433
  durationMs: Date.now() - loopStart,
432
434
  activitySummary: { feedItemsRead, mentionsFound },
433
435
  });
434
- } catch {
436
+ } catch (err) {
437
+ if (isPausedResponse(err)) throw new AgentPausedError();
435
438
  logActivity(agentDir, { type: 'error', error: 'Failed to send server heartbeat' });
436
439
  }
437
440
  }
438
441
  } catch (err) {
442
+ if (err instanceof AgentPausedError) {
443
+ logActivity(agentDir, { type: 'agent_paused', message: err.message });
444
+ console.log('\n[MyVillage] This agent has been paused by an admin. Exiting.\n');
445
+ break;
446
+ }
439
447
  logActivity(agentDir, {
440
448
  type: 'error',
441
449
  error: err.message,
@@ -583,9 +591,23 @@ function summarizeToolResult(tr) {
583
591
  return text.slice(0, 200);
584
592
  }
585
593
 
594
+ class AgentPausedError extends Error {
595
+ constructor(message = 'Agent is paused by an admin') {
596
+ super(message);
597
+ this.name = 'AgentPausedError';
598
+ this.code = 'AGENT_PAUSED';
599
+ }
600
+ }
601
+
602
+ function isPausedResponse(err) {
603
+ return err?.response?.status === 409 && err?.response?.data?.code === 'AGENT_PAUSED';
604
+ }
605
+
586
606
  // Pull up to 5 pending tasks and claim the first one we can win the race for.
587
607
  // Returns the claimed task or null. Errors are swallowed and logged — the loop
588
- // should keep running on transient backend issues.
608
+ // should keep running on transient backend issues. A pause response (409
609
+ // AGENT_PAUSED) is the one exception: rethrown as AgentPausedError so the
610
+ // outer loop can shut down cleanly.
589
611
  async function pollAndClaim(villageAgentId, agentDir) {
590
612
  try {
591
613
  const result = await listAgentTasks(villageAgentId, { status: 'PENDING', limit: 5 });
@@ -595,12 +617,15 @@ async function pollAndClaim(villageAgentId, agentDir) {
595
617
  try {
596
618
  const claim = await claimAgentTask(villageAgentId, task.id);
597
619
  return claim.data || task;
598
- } catch {
599
- // Race lost (409) or transient — try the next task
620
+ } catch (err) {
621
+ if (isPausedResponse(err)) throw new AgentPausedError();
622
+ // Race lost (409 not-paused) or transient — try the next task
600
623
  }
601
624
  }
602
625
  return null;
603
626
  } catch (err) {
627
+ if (err instanceof AgentPausedError) throw err;
628
+ if (isPausedResponse(err)) throw new AgentPausedError();
604
629
  logActivity(agentDir, { type: 'error', error: `Task poll failed: ${err.message}` });
605
630
  return null;
606
631
  }
package/src/utils/api.js CHANGED
@@ -322,6 +322,22 @@ export async function getAgentMentions(id, params = {}) {
322
322
  return response.data;
323
323
  }
324
324
 
325
+ // Agent home feed — visibility-scoped: posts from public communities OR
326
+ // communities this agent is a member of. Backend enforces scope; CLI cannot widen it.
327
+ export async function getAgentFeed(id, params = {}) {
328
+ const client = getNetworkClient();
329
+ const response = await client.get(`/agents/${id}/feed`, { params });
330
+ return response.data;
331
+ }
332
+
333
+ // Agent events — community-sourced network events the agent has visibility for.
334
+ // Source-entity scoping applied server-side.
335
+ export async function getAgentEvents(id, params = {}) {
336
+ const client = getNetworkClient();
337
+ const response = await client.get(`/agents/${id}/events`, { params });
338
+ return response.data;
339
+ }
340
+
325
341
  // Agent Activity
326
342
  export async function getAgentActivity(id, params = {}) {
327
343
  const client = getNetworkClient();