@mndrk/agx 1.4.25 → 1.4.27

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/index.js CHANGED
@@ -322,6 +322,34 @@ async function postTaskComment(taskIdOrParams, contentMaybe) {
322
322
  }
323
323
  }
324
324
 
325
+ async function postLearning(taskId, content) {
326
+ if (!taskId || !content) return;
327
+
328
+ const cloudConfig = loadCloudConfigFile();
329
+ if (!cloudConfig?.apiUrl) return;
330
+
331
+ logExecutionFlow('postLearning', 'input', `taskId=${taskId}`);
332
+
333
+ try {
334
+ await fetch(`${cloudConfig.apiUrl}/api/learnings`, {
335
+ method: 'POST',
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ ...(cloudConfig?.token ? { 'Authorization': `Bearer ${cloudConfig.token}` } : {}),
339
+ 'x-user-id': cloudConfig.userId || '',
340
+ },
341
+ body: JSON.stringify({
342
+ scope: 'task',
343
+ scope_id: taskId,
344
+ content: String(content).slice(0, 2000)
345
+ })
346
+ });
347
+ logExecutionFlow('postLearning', 'output', 'success');
348
+ } catch (err) {
349
+ logExecutionFlow('postLearning', 'output', `failed ${err?.message || err}`);
350
+ }
351
+ }
352
+
325
353
  function isLocalArtifactsEnabled() {
326
354
  // Single cutover: always on, no opt-out.
327
355
  return true;
@@ -372,6 +400,33 @@ function extractCloudTaskIdentity(task) {
372
400
  return { taskId, taskSlug };
373
401
  }
374
402
 
403
+ /**
404
+ * Extract the repo path from a task's project_context.
405
+ * Falls back to process.cwd() if no repo path is configured.
406
+ */
407
+ function extractProjectRepoPath(task) {
408
+ // Check project_context.repos (returned by /api/queue with buildTaskContext)
409
+ const repos = task?.project_context?.repos;
410
+ if (Array.isArray(repos) && repos.length > 0) {
411
+ const firstRepo = repos[0];
412
+ if (firstRepo?.path && typeof firstRepo.path === 'string' && firstRepo.path.trim()) {
413
+ let repoPath = firstRepo.path.trim();
414
+ // Expand tilde to home directory
415
+ if (repoPath.startsWith('~/')) {
416
+ repoPath = path.join(os.homedir(), repoPath.slice(2));
417
+ } else if (repoPath === '~') {
418
+ repoPath = os.homedir();
419
+ }
420
+ // Validate the path exists before using it
421
+ if (fs.existsSync(repoPath)) {
422
+ return repoPath;
423
+ }
424
+ console.log(`${c.yellow}[daemon] repo path "${repoPath}" does not exist, using cwd${c.reset}`);
425
+ }
426
+ }
427
+ return process.cwd();
428
+ }
429
+
375
430
  async function resolveLocalProjectSlugForCloudTask(storage, task) {
376
431
  const { projectId, projectSlug, projectName } = extractCloudProjectIdentity(task);
377
432
  const label = projectSlug || projectName || 'cloud';
@@ -1913,8 +1968,10 @@ async function finalizeRunSafe(storage, run, decision) {
1913
1968
  } catch { }
1914
1969
  }
1915
1970
 
1916
- async function runSingleAgentExecuteVerifyLoop({ taskId, task, provider, model, logger, storage, projectSlug, taskSlug, stageLocal, initialPromptContext, cancellationWatcher }) {
1917
- logExecutionFlow('runSingleAgentExecuteVerifyLoop', 'input', `taskId=${taskId}, provider=${provider}, model=${model}`);
1971
+ async function runSingleAgentExecuteVerifyLoop({ taskId, task, provider, model, logger, storage, projectSlug, taskSlug, stageLocal, initialPromptContext, cancellationWatcher, repoPath }) {
1972
+ logExecutionFlow('runSingleAgentExecuteVerifyLoop', 'input', `taskId=${taskId}, provider=${provider}, model=${model}, repoPath=${repoPath || 'cwd'}`);
1973
+ // Use provided repoPath or fall back to cwd
1974
+ const workingDir = repoPath || process.cwd();
1918
1975
  const stageKey = task?.stage || 'unknown';
1919
1976
  const stagePrompt = resolveStageObjective(task, stageKey, '');
1920
1977
  const stageRequirement = buildStageRequirementPrompt({ stage: stageKey, stagePrompt });
@@ -1994,9 +2051,9 @@ async function runSingleAgentExecuteVerifyLoop({ taskId, task, provider, model,
1994
2051
  await executeArtifacts.flush();
1995
2052
 
1996
2053
  // VERIFY (local commands)
1997
- const verifyCommands = detectVerifyCommands({ cwd: process.cwd() });
1998
- const gitSummary = getGitSummary({ cwd: process.cwd() });
1999
- const verifyResults = await runVerifyCommands(verifyCommands, { cwd: process.cwd(), max_output_chars: 20000 });
2054
+ const verifyCommands = detectVerifyCommands({ cwd: workingDir });
2055
+ const gitSummary = getGitSummary({ cwd: workingDir });
2056
+ const verifyResults = await runVerifyCommands(verifyCommands, { cwd: workingDir, max_output_chars: 20000 });
2000
2057
 
2001
2058
  const verifyRun = await storage.createRun({
2002
2059
  projectSlug,
@@ -2167,8 +2224,10 @@ async function runSingleAgentExecuteVerifyLoop({ taskId, task, provider, model,
2167
2224
  return { code: 1, decision: lastDecision, lastRun, runIndexEntry: lastRunEntry };
2168
2225
  }
2169
2226
 
2170
- async function runSwarmExecuteVerifyLoop({ taskId, task, logger, storage, projectSlug, taskSlug, stageLocal, initialPromptContext, cancellationWatcher }) {
2171
- logExecutionFlow('runSwarmExecuteVerifyLoop', 'input', `taskId=${taskId}`);
2227
+ async function runSwarmExecuteVerifyLoop({ taskId, task, logger, storage, projectSlug, taskSlug, stageLocal, initialPromptContext, cancellationWatcher, repoPath }) {
2228
+ logExecutionFlow('runSwarmExecuteVerifyLoop', 'input', `taskId=${taskId}, repoPath=${repoPath || 'cwd'}`);
2229
+ // Use provided repoPath or fall back to cwd
2230
+ const workingDir = repoPath || process.cwd();
2172
2231
  const stageKey = task?.stage || 'unknown';
2173
2232
  const stagePrompt = resolveStageObjective(task, stageKey, '');
2174
2233
  const stageRequirement = buildStageRequirementPrompt({ stage: stageKey, stagePrompt });
@@ -2253,9 +2312,9 @@ async function runSwarmExecuteVerifyLoop({ taskId, task, logger, storage, projec
2253
2312
  await executeArtifacts.flush();
2254
2313
 
2255
2314
  // VERIFY (local commands)
2256
- const verifyCommands = detectVerifyCommands({ cwd: process.cwd() });
2257
- const gitSummary = getGitSummary({ cwd: process.cwd() });
2258
- const verifyResults = await runVerifyCommands(verifyCommands, { cwd: process.cwd(), max_output_chars: 20000 });
2315
+ const verifyCommands = detectVerifyCommands({ cwd: workingDir });
2316
+ const gitSummary = getGitSummary({ cwd: workingDir });
2317
+ const verifyResults = await runVerifyCommands(verifyCommands, { cwd: workingDir, max_output_chars: 20000 });
2259
2318
 
2260
2319
  const verifyRun = await storage.createRun({
2261
2320
  projectSlug,
@@ -4945,8 +5004,12 @@ async function checkOnboarding() {
4945
5004
  taskSlug = await resolveLocalTaskSlugForCloudTask(storage, projectSlug, task);
4946
5005
 
4947
5006
  const cloudProject = extractCloudProjectIdentity(task);
5007
+ const taskRepoPath = extractProjectRepoPath(task);
5008
+ if (taskRepoPath !== process.cwd()) {
5009
+ console.log(`${c.cyan}[daemon] using project repo path: ${taskRepoPath}${c.reset}`);
5010
+ }
4948
5011
  await storage.writeProjectState(projectSlug, {
4949
- repo_path: process.cwd(),
5012
+ repo_path: taskRepoPath,
4950
5013
  cloud: {
4951
5014
  project_id: cloudProject.projectId,
4952
5015
  project_slug: cloudProject.projectSlug,
@@ -5030,6 +5093,7 @@ async function checkOnboarding() {
5030
5093
  stageLocal,
5031
5094
  initialPromptContext: fullPromptContext,
5032
5095
  cancellationWatcher,
5096
+ repoPath: taskRepoPath,
5033
5097
  });
5034
5098
  } else {
5035
5099
  loopResult = await runSingleAgentExecuteVerifyLoop({
@@ -5044,6 +5108,7 @@ async function checkOnboarding() {
5044
5108
  stageLocal,
5045
5109
  initialPromptContext: fullPromptContext,
5046
5110
  cancellationWatcher,
5111
+ repoPath: taskRepoPath,
5047
5112
  });
5048
5113
  }
5049
5114
 
@@ -6378,18 +6443,21 @@ async function checkOnboarding() {
6378
6443
  process.exit(0);
6379
6444
  }
6380
6445
 
6381
- // agx retry <taskId> [--task <id>] [--swarm]
6446
+ // agx retry <taskId> [--task <id>] [--swarm] [--async]
6382
6447
  if (cmd === 'retry' || (cmd === 'task' && args[1] === 'retry')) {
6383
6448
  const runArgs = cmd === 'task' ? args.slice(1) : args;
6384
6449
  retryFlowActive = true;
6385
6450
  logExecutionFlow('retry command', 'input', `cmd=${cmd}, args=${runArgs.slice(1).join(' ')}`);
6386
6451
  let taskId = null;
6387
6452
  let forceSwarm = false;
6453
+ let asyncMode = false;
6388
6454
  for (let i = 1; i < runArgs.length; i++) {
6389
6455
  if (runArgs[i] === '--task' || runArgs[i] === '-t') {
6390
6456
  taskId = runArgs[++i];
6391
6457
  } else if (runArgs[i] === '--swarm') {
6392
6458
  forceSwarm = true;
6459
+ } else if (runArgs[i] === '--async' || runArgs[i] === '-a') {
6460
+ asyncMode = true;
6393
6461
  }
6394
6462
  }
6395
6463
  if (!taskId) {
@@ -6397,12 +6465,26 @@ async function checkOnboarding() {
6397
6465
  }
6398
6466
  if (!taskId) {
6399
6467
  logExecutionFlow('retry command', 'output', 'missing task id');
6400
- console.log(`${c.yellow}Usage:${c.reset} agx retry <taskId> [--task <id>] [--swarm]`);
6401
- console.log(`${c.dim} or:${c.reset} agx task retry <taskId> [--task <id>] [--swarm]`);
6468
+ console.log(`${c.yellow}Usage:${c.reset} agx retry <taskId> [--task <id>] [--swarm] [--async]`);
6469
+ console.log(`${c.dim} or:${c.reset} agx task retry <taskId> [--task <id>] [--swarm] [--async]`);
6470
+ console.log(`${c.dim}--async: Reset status and let daemon handle (non-blocking)${c.reset}`);
6402
6471
  process.exit(1);
6403
6472
  }
6404
6473
 
6405
6474
  try {
6475
+ // Async mode: just reset task status, daemon will pick it up
6476
+ if (asyncMode) {
6477
+ const resolvedId = await resolveTaskId(taskId);
6478
+ await cloudRequest('PATCH', `/api/tasks/${resolvedId}`, {
6479
+ status: 'queued',
6480
+ started_at: null,
6481
+ completed_at: null,
6482
+ });
6483
+ console.log(`${c.green}✓${c.reset} Task ${resolvedId.slice(0, 8)} queued for retry`);
6484
+ console.log(`${c.dim}Daemon will pick it up shortly${c.reset}`);
6485
+ process.exit(0);
6486
+ }
6487
+
6406
6488
  const exitCode = await runTaskInline(taskId, { resetFirst: true, forceSwarm });
6407
6489
  process.exit(exitCode);
6408
6490
  } catch (err) {
@@ -7421,7 +7503,7 @@ PROVIDERS:
7421
7503
  CLOUD:
7422
7504
  agx new "<task>" Create task in cloud
7423
7505
  agx run <id|slug|#> Claim and run a task
7424
- agx retry <id|slug|#> Reset + retry a task
7506
+ agx retry <id|slug|#> Reset + retry a task (--async for non-blocking)
7425
7507
  agx status Show cloud status
7426
7508
  agx complete <taskId> Mark task stage complete
7427
7509
  agx project assign <project> --task <task> Assign task to project
package/lib/cli/runCli.js CHANGED
@@ -1485,18 +1485,21 @@ async function checkOnboarding() {
1485
1485
  process.exit(0);
1486
1486
  }
1487
1487
 
1488
- // agx retry <taskId> [--task <id>] [--swarm]
1488
+ // agx retry <taskId> [--task <id>] [--swarm] [--async]
1489
1489
  if (cmd === 'retry' || (cmd === 'task' && args[1] === 'retry')) {
1490
1490
  const runArgs = cmd === 'task' ? args.slice(1) : args;
1491
1491
  retryFlowActive = true;
1492
1492
  logExecutionFlow('retry command', 'input', `cmd=${cmd}, args=${runArgs.slice(1).join(' ')}`);
1493
1493
  let taskId = null;
1494
1494
  let forceSwarm = false;
1495
+ let asyncMode = false;
1495
1496
  for (let i = 1; i < runArgs.length; i++) {
1496
1497
  if (runArgs[i] === '--task' || runArgs[i] === '-t') {
1497
1498
  taskId = runArgs[++i];
1498
1499
  } else if (runArgs[i] === '--swarm') {
1499
1500
  forceSwarm = true;
1501
+ } else if (runArgs[i] === '--async' || runArgs[i] === '-a') {
1502
+ asyncMode = true;
1500
1503
  }
1501
1504
  }
1502
1505
  if (!taskId) {
@@ -1504,12 +1507,27 @@ async function checkOnboarding() {
1504
1507
  }
1505
1508
  if (!taskId) {
1506
1509
  logExecutionFlow('retry command', 'output', 'missing task id');
1507
- console.log(`${c.yellow}Usage:${c.reset} agx retry <taskId> [--task <id>] [--swarm]`);
1508
- console.log(`${c.dim} or:${c.reset} agx task retry <taskId> [--task <id>] [--swarm]`);
1510
+ console.log(`${c.yellow}Usage:${c.reset} agx retry <taskId> [--task <id>] [--swarm] [--async]`);
1511
+ console.log(`${c.dim} or:${c.reset} agx task retry <taskId> [--task <id>] [--swarm] [--async]`);
1512
+ console.log(`${c.dim}--async: Reset status and let daemon handle (non-blocking)${c.reset}`);
1509
1513
  process.exit(1);
1510
1514
  }
1511
1515
 
1512
1516
  try {
1517
+ // Async mode: just reset task status, daemon will pick it up
1518
+ logExecutionFlow('retry command', 'processing', `asyncMode=${asyncMode}, taskId=${taskId}`);
1519
+ if (asyncMode) {
1520
+ const resolvedId = await resolveTaskId(taskId);
1521
+ await cloudRequest('PATCH', `/api/tasks/${resolvedId}`, {
1522
+ status: 'queued',
1523
+ started_at: null,
1524
+ completed_at: null,
1525
+ });
1526
+ console.log(`${c.green}✓${c.reset} Task ${resolvedId.slice(0, 8)} queued for retry`);
1527
+ console.log(`${c.dim}Daemon will pick it up shortly${c.reset}`);
1528
+ process.exit(0);
1529
+ }
1530
+
1513
1531
  const exitCode = await runTaskInline(taskId, { resetFirst: true, forceSwarm });
1514
1532
  process.exit(exitCode);
1515
1533
  } catch (err) {
@@ -2500,7 +2518,7 @@ PROVIDERS:
2500
2518
  CLOUD:
2501
2519
  agx new "<task>" Create task in cloud
2502
2520
  agx run <id|slug|#> Claim and run a task
2503
- agx retry <id|slug|#> Reset + retry a task
2521
+ agx retry <id|slug|#> Reset + retry a task (--async for non-blocking)
2504
2522
  agx status Show cloud status
2505
2523
  agx complete <taskId> Mark task stage complete
2506
2524
  agx project assign <project> --task <task> Assign task to project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mndrk/agx",
3
- "version": "1.4.25",
3
+ "version": "1.4.27",
4
4
  "description": "Autonomous AI Agent Orchestrator for Claude, Gemini, and Ollama",
5
5
  "main": "lib/index.js",
6
6
  "exports": {