@myvillage/cli 1.30.0 → 1.31.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.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,8 @@ import { getMCPTools, cleanupMCPClients } from './mcp-client.js';
12
12
  import { gatherContext } from './context.js';
13
13
  import { isWithinActiveHours, getNextCheckInMs } from './scheduler.js';
14
14
  import { parse as parseYaml } from 'yaml';
15
- import { postAgentHeartbeat, listAgentTasks, claimAgentTask, completeAgentTask } from '../utils/api.js';
15
+ import { postAgentHeartbeat, listAgentTasks, claimAgentTask, completeAgentTask, refreshAccessToken } from '../utils/api.js';
16
+ import { getAccessToken, isTokenExpired } from '../utils/auth.js';
16
17
  import { readAgentWisdom } from '../utils/wisdom.js';
17
18
 
18
19
  export async function agentLoop(agentName, { signal }) {
@@ -115,6 +116,29 @@ export async function agentLoop(agentName, { signal }) {
115
116
  logActivity(agentDir, { type: 'loop_start', iteration });
116
117
  updateHeartbeat(agentDir);
117
118
 
119
+ // Refresh OAuth token and reconnect MCP if the access token rotated.
120
+ // Why: the daemon is started with a snapshot of MYVILLAGE_ACCESS_TOKEN in
121
+ // env (see agent-local.js). The MCP client baked that token into its
122
+ // Authorization header at construction time and has no refresh path of
123
+ // its own — so once the OAuth token expires (~1h) every MCP tool call
124
+ // 401s until the daemon is restarted. Re-read credentials each loop and
125
+ // swap the MCP client when the token changes.
126
+ try {
127
+ if (isTokenExpired()) {
128
+ await refreshAccessToken();
129
+ }
130
+ const currentToken = getAccessToken();
131
+ if (currentToken && currentToken !== process.env.MYVILLAGE_ACCESS_TOKEN) {
132
+ process.env.MYVILLAGE_ACCESS_TOKEN = currentToken;
133
+ await cleanupMCPClients();
134
+ const refreshed = await getMCPTools(agentDir, config);
135
+ tools = refreshed.tools;
136
+ logActivity(agentDir, { type: 'mcp_reconnected', reason: 'token rotated' });
137
+ }
138
+ } catch (err) {
139
+ logActivity(agentDir, { type: 'error', error: `Token refresh / MCP reconnect failed: ${err.message}` });
140
+ }
141
+
118
142
  // Activity counters for this iteration
119
143
  const activity = {
120
144
  postsCreated: 0,
@@ -335,16 +359,30 @@ Guidelines:
335
359
  // whether the action tools actually succeeded — not on the model's
336
360
  // self-report. The LLM sometimes claims "I posted!" after a tool error.
337
361
  if (activeTask && config.man?.village_agent_id) {
362
+ // Three independent failure signals:
363
+ // 1. action tools tried and all failed (original signal — still right)
364
+ // 2. any tool errored AND no action tool succeeded (e.g. an
365
+ // MCP/auth error during discovery means the LLM never got to
366
+ // call an action tool — older versions marked these COMPLETED)
367
+ // 3. the LLM explicitly declared failure in its final text
368
+ // (matches the `**Task Failed:` sentinel the model emits when
369
+ // it gives up after repeated tool errors)
370
+ const llmDeclaredFailure = /^\s*\*\*Task Failed/i.test(result.text || '');
371
+ const hasToolErrors = taskActionAudit.toolErrors.length > 0;
338
372
  const shouldFail =
339
- taskActionAudit.actionToolsCalled > 0 &&
340
- taskActionAudit.actionToolsSucceeded === 0;
373
+ (taskActionAudit.actionToolsCalled > 0 && taskActionAudit.actionToolsSucceeded === 0) ||
374
+ (hasToolErrors && taskActionAudit.actionToolsSucceeded === 0) ||
375
+ llmDeclaredFailure;
341
376
 
342
377
  try {
343
378
  if (shouldFail) {
344
379
  const firstError = taskActionAudit.toolErrors[0];
380
+ const firstLine = (result.text || '').split('\n').find(l => l.trim()) || '';
345
381
  const errorMessage = firstError
346
382
  ? `${firstError.tool} failed: ${firstError.message}`
347
- : 'Action tools called but all failed';
383
+ : llmDeclaredFailure
384
+ ? firstLine.replace(/^\*\*|\*\*$/g, '').slice(0, 300) || 'Agent declared task failed'
385
+ : 'Action tools called but all failed';
348
386
  await completeAgentTask(config.man.village_agent_id, activeTask.id, {
349
387
  errorMessage,
350
388
  output: {
package/src/utils/api.js CHANGED
@@ -50,7 +50,7 @@ export function createClient(baseURL) {
50
50
  return client;
51
51
  }
52
52
 
53
- async function refreshAccessToken() {
53
+ export async function refreshAccessToken() {
54
54
  const config = getConfig();
55
55
  const creds = loadCredentials();
56
56