@myvillage/cli 1.84.0 → 1.85.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/src/agent-runtime/context.js +21 -95
- package/src/agent-runtime/loop.js +36 -7
- package/src/utils/api.js +18 -5
package/package.json
CHANGED
|
@@ -1,103 +1,25 @@
|
|
|
1
1
|
// ── Context Gathering ───────────────────────────────────
|
|
2
2
|
// Assembles a context snapshot for each agent loop iteration.
|
|
3
3
|
// This becomes the user message sent to the LLM.
|
|
4
|
+
//
|
|
5
|
+
// NOTE: This intentionally does NOT auto-fetch the community feed, network
|
|
6
|
+
// events, or mentions. Pushing that data into every loop of every agent was
|
|
7
|
+
// wasteful — most iterations don't act on it, yet each one cost three
|
|
8
|
+
// uncached HTTP round-trips (feed + events + mentions) plus the tokens to
|
|
9
|
+
// render them. Community activity is now pull-on-demand: an agent whose job
|
|
10
|
+
// involves the network requests it through its MCP tools (network_feed,
|
|
11
|
+
// network_search, network_post_get, network_comment_list, …). The instruction
|
|
12
|
+
// block below tells the agent how and when to go look.
|
|
4
13
|
|
|
5
|
-
import { getAgentFeed, getAgentMentions, getAgentEvents } from '../utils/api.js';
|
|
6
14
|
import { listDatasets } from '../utils/dataset-workspace.js';
|
|
7
15
|
import { listModels } from '../utils/model-workspace.js';
|
|
8
16
|
|
|
9
17
|
export async function gatherContext(agentConfig, lastCheckIn, recentActions = []) {
|
|
10
18
|
const parts = [];
|
|
11
|
-
let mentionsCount = 0;
|
|
12
|
-
let eventsCount = 0;
|
|
13
19
|
|
|
14
20
|
parts.push(`Current time: ${new Date().toISOString()}`);
|
|
15
21
|
parts.push(`Last check-in: ${lastCheckIn || 'never'}`);
|
|
16
22
|
|
|
17
|
-
const agentId = agentConfig.man?.agent_id;
|
|
18
|
-
|
|
19
|
-
// Fetch new feed activity since last check-in. The agent-scoped feed
|
|
20
|
-
// endpoint is enforced server-side: posts from public communities OR
|
|
21
|
-
// communities this agent is a member of. Caller cannot widen the scope.
|
|
22
|
-
if (agentId) {
|
|
23
|
-
try {
|
|
24
|
-
const params = { pageSize: 30 };
|
|
25
|
-
if (lastCheckIn) {
|
|
26
|
-
params.since = lastCheckIn;
|
|
27
|
-
}
|
|
28
|
-
const result = await getAgentFeed(agentId, params);
|
|
29
|
-
const items = result.data || result;
|
|
30
|
-
|
|
31
|
-
if (Array.isArray(items) && items.length > 0) {
|
|
32
|
-
parts.push(`\nNew feed activity (${items.length} items):`);
|
|
33
|
-
for (const item of items.slice(0, 20)) {
|
|
34
|
-
const author = item.agentProfile?.handle
|
|
35
|
-
|| item.villager?.firstName
|
|
36
|
-
|| 'unknown';
|
|
37
|
-
const community = item.community?.slug || '?';
|
|
38
|
-
const preview = (item.body || item.title || '').slice(0, 120);
|
|
39
|
-
parts.push(`- @${author} in r/${community}: ${preview}`);
|
|
40
|
-
}
|
|
41
|
-
if (items.length > 20) {
|
|
42
|
-
parts.push(`... and ${items.length - 20} more items`);
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
parts.push('\nNo new feed activity since last check-in.');
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
parts.push('\nCould not fetch feed (network error).');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Fetch network events (NEW_POST, NEW_MEMBER, etc.) the agent has visibility for.
|
|
52
|
-
try {
|
|
53
|
-
const eventParams = { pageSize: 20 };
|
|
54
|
-
if (lastCheckIn) eventParams.since = lastCheckIn;
|
|
55
|
-
const eventResult = await getAgentEvents(agentId, eventParams);
|
|
56
|
-
const events = eventResult.data || eventResult;
|
|
57
|
-
|
|
58
|
-
if (Array.isArray(events) && events.length > 0) {
|
|
59
|
-
eventsCount = events.length;
|
|
60
|
-
parts.push(`\nNetwork events since last check-in (${events.length}):`);
|
|
61
|
-
for (const e of events.slice(0, 10)) {
|
|
62
|
-
parts.push(`- [${e.eventType}] source=${e.sourceEntityType}:${e.sourceEntityId}`);
|
|
63
|
-
}
|
|
64
|
-
if (events.length > 10) {
|
|
65
|
-
parts.push(`... and ${events.length - 10} more events`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// Events fetch failed — not critical, skip silently
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Fetch mentions since last check-in
|
|
73
|
-
try {
|
|
74
|
-
const mentionParams = { pageSize: 20 };
|
|
75
|
-
if (lastCheckIn) mentionParams.since = lastCheckIn;
|
|
76
|
-
const mentionResult = await getAgentMentions(agentId, mentionParams);
|
|
77
|
-
const mentions = mentionResult.data || mentionResult;
|
|
78
|
-
|
|
79
|
-
if (Array.isArray(mentions) && mentions.length > 0) {
|
|
80
|
-
mentionsCount = mentions.length;
|
|
81
|
-
parts.push(`\nMentions since last check-in (${mentions.length}):`);
|
|
82
|
-
for (const m of mentions.slice(0, 10)) {
|
|
83
|
-
const author = m.agentProfile?.handle
|
|
84
|
-
|| m.villager?.firstName
|
|
85
|
-
|| 'unknown';
|
|
86
|
-
const preview = (m.body || m.title || '').slice(0, 120);
|
|
87
|
-
const type = m.postId ? 'comment' : 'post';
|
|
88
|
-
parts.push(`- [${type}] @${author}: ${preview}`);
|
|
89
|
-
}
|
|
90
|
-
if (mentions.length > 10) {
|
|
91
|
-
parts.push(`... and ${mentions.length - 10} more mentions`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
// Mentions fetch failed — not critical, skip silently
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
parts.push('\nNot registered on MAN feed yet.');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
23
|
// Include recent actions so the agent knows what it already did
|
|
102
24
|
if (recentActions.length > 0) {
|
|
103
25
|
parts.push('\nYour recent actions:');
|
|
@@ -139,15 +61,19 @@ export async function gatherContext(agentConfig, lastCheckIn, recentActions = []
|
|
|
139
61
|
/* no local models / store unavailable — skip */
|
|
140
62
|
}
|
|
141
63
|
|
|
142
|
-
|
|
64
|
+
// Behavioral instructions.
|
|
65
|
+
//
|
|
66
|
+
// Community activity (feed / events / mentions) is deliberately NOT included
|
|
67
|
+
// above — it is pulled on demand instead. If engaging with the network is
|
|
68
|
+
// part of this agent's purpose, it must go look for itself using whatever
|
|
69
|
+
// community tools it has been granted.
|
|
70
|
+
parts.push('\nDecide what to do this iteration based on your purpose, your personality, and any active task.');
|
|
71
|
+
parts.push('Community activity (posts, comments, mentions) is NOT included above — nothing is pushed to you automatically. If engaging with the community is part of your job, use your tools to look: e.g. network_feed for recent activity, network_search to find specific posts or mentions of you, and network_post_get / network_comment_list to read a thread before replying. (Only relevant if those tools are available to you.)');
|
|
72
|
+
parts.push('Fetch only what you actually need this iteration, and only if your purpose calls for it — do not pull the feed just because you can.');
|
|
143
73
|
parts.push('IMPORTANT: If you already commented on a post, only comment again if someone ELSE has replied since your last comment. Do not leave multiple unanswered comments on the same post.');
|
|
144
|
-
parts.push('
|
|
145
|
-
parts.push('You may post, comment, vote, read files, or do nothing if appropriate.');
|
|
74
|
+
parts.push('Doing nothing is a valid choice — if nothing needs your attention right now, take no action.');
|
|
75
|
+
parts.push('You may post, comment, vote, read files, run your tools, or do nothing if appropriate.');
|
|
146
76
|
parts.push('Be thoughtful — quality over quantity. Avoid repeating yourself.');
|
|
147
77
|
|
|
148
|
-
return {
|
|
149
|
-
text: parts.join('\n'),
|
|
150
|
-
mentionsCount,
|
|
151
|
-
eventsCount,
|
|
152
|
-
};
|
|
78
|
+
return { text: parts.join('\n') };
|
|
153
79
|
}
|
|
@@ -257,11 +257,13 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
257
257
|
activeTask = await pollAndClaim(config.man.village_agent_id, agentDir);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
// Gather context (returns { text
|
|
260
|
+
// Gather context (returns { text }). Community feed/events/mentions are
|
|
261
|
+
// no longer auto-fetched here — the agent pulls them on demand via its
|
|
262
|
+
// tools — so these activity counters stay at 0 unless a future signal
|
|
263
|
+
// repopulates them.
|
|
261
264
|
const contextResult = await gatherContext(config, lastCheckIn, recentActions);
|
|
262
265
|
let context = contextResult.text;
|
|
263
|
-
|
|
264
|
-
const eventsFound = contextResult.eventsCount || 0;
|
|
266
|
+
const eventsFound = 0;
|
|
265
267
|
|
|
266
268
|
if (activeTask) {
|
|
267
269
|
// A claimed task takes over the iteration. The default monitoring
|
|
@@ -279,16 +281,16 @@ Guidelines:
|
|
|
279
281
|
- 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.
|
|
280
282
|
- Do not stop after one tool call unless that call alone fulfills the entire instruction.
|
|
281
283
|
- Do not ask clarifying questions back to the user; make a reasonable interpretation and proceed.
|
|
282
|
-
- The
|
|
284
|
+
- The ambient context below is provided only for situational awareness — carry out the task regardless of what it contains.
|
|
283
285
|
- 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.`;
|
|
284
286
|
|
|
285
|
-
context = `## TASK (id=${activeTask.id}, type=${activeTask.taskType})\n${instructionText}\n\n---\n\n##
|
|
287
|
+
context = `## TASK (id=${activeTask.id}, type=${activeTask.taskType})\n${instructionText}\n\n---\n\n## AMBIENT CONTEXT (for awareness only)\n${context}`;
|
|
286
288
|
|
|
287
289
|
logActivity(agentDir, { type: 'task_claimed', taskId: activeTask.id, taskType: activeTask.taskType });
|
|
288
290
|
}
|
|
289
291
|
|
|
290
|
-
//
|
|
291
|
-
feedItemsRead
|
|
292
|
+
// Feed is no longer pushed into context; nothing to count here.
|
|
293
|
+
// feedItemsRead / mentionsFound stay 0 (see declarations above).
|
|
292
294
|
|
|
293
295
|
logActivity(agentDir, {
|
|
294
296
|
type: 'context',
|
|
@@ -633,6 +635,25 @@ const ACTION_TOOLS = new Set([
|
|
|
633
635
|
'task_assign',
|
|
634
636
|
'task_complete',
|
|
635
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',
|
|
636
657
|
]);
|
|
637
658
|
|
|
638
659
|
function flattenToolResultText(tr) {
|
|
@@ -664,6 +685,14 @@ function isToolResultError(tr) {
|
|
|
664
685
|
// "the user was unauthorized to do X" because we anchor on word boundaries.
|
|
665
686
|
const text = flattenToolResultText(tr);
|
|
666
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
|
+
}
|
|
667
696
|
return /\b(40[0-9]|50[0-9])\b/.test(text) ||
|
|
668
697
|
/\b(not found|unauthorized|forbidden|invalid|insufficient_quota|authentication failed)\b/i.test(text);
|
|
669
698
|
}
|
package/src/utils/api.js
CHANGED
|
@@ -689,11 +689,24 @@ export async function claimAgentTask(villageAgentId, taskId) {
|
|
|
689
689
|
// otherwise the task is marked COMPLETED with the given `output`.
|
|
690
690
|
export async function completeAgentTask(villageAgentId, taskId, data = {}) {
|
|
691
691
|
const client = getPlatformClient();
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
692
|
+
try {
|
|
693
|
+
const response = await client.post(
|
|
694
|
+
`/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/complete`,
|
|
695
|
+
data,
|
|
696
|
+
);
|
|
697
|
+
return response.data;
|
|
698
|
+
} catch (err) {
|
|
699
|
+
// Idempotent completion. The task may already be terminal because the agent
|
|
700
|
+
// completed it itself via the `agent_task_complete` tool during its run (the
|
|
701
|
+
// daemon also completes the active task at the end of each iteration), or a
|
|
702
|
+
// retry raced. A 409 "already terminal" means the task reached the end state
|
|
703
|
+
// we wanted — treat it as success rather than a failure so the daemon doesn't
|
|
704
|
+
// log a spurious "Failed to mark task complete" error every run.
|
|
705
|
+
if (err?.response?.status === 409) {
|
|
706
|
+
return { alreadyTerminal: true, message: err.response?.data?.error };
|
|
707
|
+
}
|
|
708
|
+
throw err;
|
|
709
|
+
}
|
|
697
710
|
}
|
|
698
711
|
|
|
699
712
|
// Retry a single FAILED or CANCELLED task: resets it to PENDING so the
|