@renseiai/agentfactory-cli 0.8.7 → 0.8.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,QAAA,MAAM,OAAO,QAAkB,CAAA;AAE/B,iBAAS,SAAS,IAAI,IAAI,CAwBzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,QAAA,MAAM,OAAO,QAAkB,CAAA;AAE/B,iBAAS,SAAS,IAAI,IAAI,CAyBzB"}
package/dist/src/index.js CHANGED
@@ -25,6 +25,7 @@ Commands:
25
25
  analyze-logs Analyze agent session logs for errors
26
26
  linear Linear issue tracker operations
27
27
  sync-routes Generate missing route and page files from manifest
28
+ status Show fleet status (inline one-line summary)
28
29
  help Show this help message
29
30
 
30
31
  Run 'agentfactory <command> --help' for command-specific options.
@@ -63,6 +64,9 @@ switch (command) {
63
64
  case 'sync-routes':
64
65
  import('./sync-routes');
65
66
  break;
67
+ case 'status':
68
+ import('./status');
69
+ break;
66
70
  case 'help':
67
71
  case '--help':
68
72
  case '-h':
@@ -1 +1 @@
1
- {"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/agent-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAE5E,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,OAAO,EAAE,YAAY,CAAA;IACrB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAMD,eAAO,MAAM,CAAC;;;;;;;CAOJ,CAAA;AAuRV,wBAAsB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBvE"}
1
+ {"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/agent-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAE5E,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,OAAO,EAAE,YAAY,CAAA;IACrB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAMD,eAAO,MAAM,CAAC;;;;;;;CAOJ,CAAA;AA8RV,wBAAsB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBvE"}
@@ -5,7 +5,7 @@
5
5
  * agent sessions. Works by updating Redis state directly — workers poll for
6
6
  * status changes and pending prompts every 5 seconds.
7
7
  */
8
- import { getRedisClient, getAllSessions, updateSessionStatus, storeSessionState, storePendingPrompt, disconnectRedis, } from '@renseiai/agentfactory-server';
8
+ import { getRedisClient, getAllSessions, updateSessionStatus, storeSessionState, publishUrgent, disconnectRedis, } from '@renseiai/agentfactory-server';
9
9
  import { createLinearAgentClient } from '@renseiai/plugin-linear';
10
10
  // ---------------------------------------------------------------------------
11
11
  // ANSI colors
@@ -114,12 +114,23 @@ async function chatWithAgent(issueId, message) {
114
114
  await disconnectRedis();
115
115
  return;
116
116
  }
117
- const prompt = await storePendingPrompt(session.linearSessionId, session.issueId, message);
118
- if (prompt) {
119
- console.log(`${C.green}Message queued${C.reset} (id: ${prompt.id})worker will pick up within ~5 seconds`);
117
+ const agentId = session.agentId;
118
+ if (!agentId) {
119
+ console.error(`${C.red}Session has no agentIdcannot publish to inbox${C.reset}`);
120
+ await disconnectRedis();
121
+ return;
120
122
  }
121
- else {
122
- console.error(`${C.red}Failed to store pending prompt${C.reset}`);
123
+ try {
124
+ const streamId = await publishUrgent(agentId, {
125
+ type: 'directive',
126
+ sessionId: session.linearSessionId,
127
+ payload: message,
128
+ createdAt: Date.now(),
129
+ });
130
+ console.log(`${C.green}Message queued${C.reset} (id: ${streamId}) — worker will pick up within ~5 seconds`);
131
+ }
132
+ catch (err) {
133
+ console.error(`${C.red}Failed to publish to inbox: ${err instanceof Error ? err.message : String(err)}${C.reset}`);
123
134
  }
124
135
  await disconnectRedis();
125
136
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Status Runner -- Programmatic API for the af-status CLI.
3
+ *
4
+ * Provides fleet status via the AgentFactory public API.
5
+ * In TTY mode, spawns the Go `af-status` binary for rich inline output.
6
+ * In piped/JSON mode, fetches stats directly from Node.js.
7
+ */
8
+ export interface StatusRunnerConfig {
9
+ /** Output raw JSON instead of human-readable format */
10
+ json?: boolean;
11
+ /** Enable auto-refresh watch mode */
12
+ watch?: boolean;
13
+ /** Watch interval (e.g., "1s", "5s") — only used with watch */
14
+ interval?: string;
15
+ /** API base URL override */
16
+ url?: string;
17
+ }
18
+ export declare const C: {
19
+ readonly reset: "\u001B[0m";
20
+ readonly red: "\u001B[31m";
21
+ readonly green: "\u001B[32m";
22
+ readonly yellow: "\u001B[33m";
23
+ readonly cyan: "\u001B[36m";
24
+ readonly gray: "\u001B[90m";
25
+ };
26
+ export declare function runStatus(config: StatusRunnerConfig): Promise<void>;
27
+ //# sourceMappingURL=status-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/status-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAMD,eAAO,MAAM,CAAC;;;;;;;CAOJ,CAAA;AAuEV,wBAAsB,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzE"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Status Runner -- Programmatic API for the af-status CLI.
3
+ *
4
+ * Provides fleet status via the AgentFactory public API.
5
+ * In TTY mode, spawns the Go `af-status` binary for rich inline output.
6
+ * In piped/JSON mode, fetches stats directly from Node.js.
7
+ */
8
+ import { spawn } from 'child_process';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ // ---------------------------------------------------------------------------
14
+ // ANSI colors (same as agent-runner)
15
+ // ---------------------------------------------------------------------------
16
+ export const C = {
17
+ reset: '\x1b[0m',
18
+ red: '\x1b[31m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ cyan: '\x1b[36m',
22
+ gray: '\x1b[90m',
23
+ };
24
+ // ---------------------------------------------------------------------------
25
+ // Helpers
26
+ // ---------------------------------------------------------------------------
27
+ function getBaseURL(override) {
28
+ return override ?? process.env.WORKER_API_URL ?? 'http://localhost:3000';
29
+ }
30
+ async function fetchStats(baseURL) {
31
+ const url = `${baseURL.replace(/\/+$/, '')}/api/public/stats`;
32
+ const resp = await fetch(url);
33
+ if (!resp.ok) {
34
+ throw new Error(`API request failed: ${resp.status} ${resp.statusText}`);
35
+ }
36
+ return resp.json();
37
+ }
38
+ function resolveGoBinary() {
39
+ // Go binary lives in packages/tui/bin/af-status relative to the CLI package
40
+ return path.resolve(__dirname, '../../tui/bin/af-status');
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Command handlers
44
+ // ---------------------------------------------------------------------------
45
+ async function showJSON(baseURL) {
46
+ const stats = await fetchStats(baseURL);
47
+ console.log(JSON.stringify(stats, null, 2));
48
+ }
49
+ function spawnGoBinary(config, baseURL) {
50
+ return new Promise((resolve, reject) => {
51
+ const binPath = resolveGoBinary();
52
+ const args = ['--url', baseURL];
53
+ if (config.json)
54
+ args.push('--json');
55
+ if (config.watch)
56
+ args.push('--watch');
57
+ if (config.interval)
58
+ args.push('--interval', config.interval);
59
+ const child = spawn(binPath, args, {
60
+ stdio: 'inherit',
61
+ });
62
+ child.on('error', (err) => {
63
+ if (err.code === 'ENOENT') {
64
+ // Go binary not built yet — fall back to Node.js JSON output
65
+ console.error(`${C.yellow}Go binary not found at ${binPath}${C.reset}`);
66
+ console.error(`${C.gray}Falling back to JSON output. Build with: cd packages/tui && make build-status${C.reset}`);
67
+ showJSON(baseURL).then(resolve, reject);
68
+ }
69
+ else {
70
+ reject(err);
71
+ }
72
+ });
73
+ child.on('close', (code) => {
74
+ if (code === 0) {
75
+ resolve();
76
+ }
77
+ else {
78
+ reject(new Error(`af-status exited with code ${code}`));
79
+ }
80
+ });
81
+ });
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Public entry point
85
+ // ---------------------------------------------------------------------------
86
+ export async function runStatus(config) {
87
+ const baseURL = getBaseURL(config.url);
88
+ const isTTY = process.stdout.isTTY ?? false;
89
+ // If piped (non-TTY) and not explicitly requesting watch, default to JSON
90
+ if (!isTTY && !config.watch) {
91
+ await showJSON(baseURL);
92
+ return;
93
+ }
94
+ // If --json flag without watch, just fetch and print
95
+ if (config.json && !config.watch) {
96
+ await showJSON(baseURL);
97
+ return;
98
+ }
99
+ // TTY mode or watch mode — delegate to Go binary
100
+ await spawnGoBinary(config, baseURL);
101
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAwED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAy7Bf"}
1
+ {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AA4ED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAi/Bf"}
@@ -245,7 +245,7 @@ export async function runWorker(config, signal) {
245
245
  if (result.data) {
246
246
  consecutiveHeartbeatFailures = 0;
247
247
  if (claimFailureCount > 0) {
248
- log.info('Claim race summary since last heartbeat', { claimFailures: claimFailureCount });
248
+ log.debug('Claim race summary since last heartbeat', { claimFailures: claimFailureCount });
249
249
  claimFailureCount = 0;
250
250
  }
251
251
  log.debug('Heartbeat acknowledged', {
@@ -277,25 +277,26 @@ export async function runWorker(config, signal) {
277
277
  // -----------------------------------------------------------------------
278
278
  async function pollForWork() {
279
279
  if (!workerId)
280
- return { work: [], pendingPrompts: {}, hasPendingPrompts: false };
280
+ return { work: [], inboxMessages: {}, hasInboxMessages: false };
281
281
  const result = await apiRequestWithError(`/api/workers/${workerId}/poll`);
282
282
  if (result.error?.type === 'worker_not_found') {
283
283
  await attemptReregistration();
284
- return { work: [], pendingPrompts: {}, hasPendingPrompts: false };
284
+ return { work: [], inboxMessages: {}, hasInboxMessages: false };
285
285
  }
286
286
  if (!result.data) {
287
- return { work: [], pendingPrompts: {}, hasPendingPrompts: false };
287
+ return { work: [], inboxMessages: {}, hasInboxMessages: false };
288
288
  }
289
289
  const pollData = result.data;
290
- if (pollData.hasPendingPrompts) {
291
- const totalPrompts = Object.values(pollData.pendingPrompts).reduce((sum, prompts) => sum + prompts.length, 0);
292
- log.info('Received pending prompts', {
293
- sessionCount: Object.keys(pollData.pendingPrompts).length,
294
- totalPrompts,
295
- sessions: Object.entries(pollData.pendingPrompts).map(([sessionId, prompts]) => ({
290
+ if (pollData.hasInboxMessages) {
291
+ const totalMessages = Object.values(pollData.inboxMessages).reduce((sum, messages) => sum + messages.length, 0);
292
+ log.info('Received inbox messages', {
293
+ sessionCount: Object.keys(pollData.inboxMessages).length,
294
+ totalMessages,
295
+ sessions: Object.entries(pollData.inboxMessages).map(([sessionId, messages]) => ({
296
296
  sessionId: sessionId.substring(0, 8),
297
- promptCount: prompts.length,
298
- promptIds: prompts.map((p) => p.id),
297
+ messageCount: messages.length,
298
+ messageIds: messages.map((m) => m.id),
299
+ types: messages.map((m) => m.type),
299
300
  })),
300
301
  });
301
302
  }
@@ -309,6 +310,15 @@ export async function runWorker(config, signal) {
309
310
  body: JSON.stringify({ workerId }),
310
311
  });
311
312
  }
313
+ async function ackInboxMessage(sessionId, message) {
314
+ await apiRequest(`/api/sessions/${sessionId}/inbox/ack`, {
315
+ method: 'POST',
316
+ body: JSON.stringify({
317
+ messageId: message.id,
318
+ lane: message.lane,
319
+ }),
320
+ });
321
+ }
312
322
  async function reportStatus(sessionId, status, extra) {
313
323
  if (!workerId)
314
324
  return;
@@ -672,10 +682,27 @@ export async function runWorker(config, signal) {
672
682
  log.info(`Found ${pollResult.work.length} work item(s)`, {
673
683
  activeCount,
674
684
  availableCapacity,
685
+ preClaimed: pollResult.preClaimed ?? false,
675
686
  });
676
687
  for (const item of pollResult.work.slice(0, availableCapacity)) {
677
688
  if (!running)
678
689
  break;
690
+ // Server-side claiming: items are already claimed during poll
691
+ if (pollResult.preClaimed) {
692
+ log.status('claimed', item.issueIdentifier);
693
+ if (workerConfig.dryRun) {
694
+ log.info(`[DRY RUN] Would execute: ${item.issueIdentifier}`);
695
+ }
696
+ else {
697
+ executeWork(item).catch((error) => {
698
+ log.error('Background work execution failed', {
699
+ error: error instanceof Error ? error.message : String(error),
700
+ });
701
+ });
702
+ }
703
+ continue;
704
+ }
705
+ // Legacy client-side claiming (fallback for older servers)
679
706
  const claimResult = await claimWork(item.sessionId);
680
707
  if (claimResult?.claimed) {
681
708
  log.status('claimed', item.issueIdentifier);
@@ -696,68 +723,94 @@ export async function runWorker(config, signal) {
696
723
  }
697
724
  }
698
725
  }
699
- // Handle pending prompts for active sessions
700
- if (pollResult.hasPendingPrompts) {
701
- for (const [sessionId, prompts] of Object.entries(pollResult.pendingPrompts)) {
702
- for (const prompt of prompts) {
703
- log.info('Processing pending prompt', {
726
+ // Handle inbox messages for active sessions (urgent-first)
727
+ if (pollResult.hasInboxMessages) {
728
+ for (const [sessionId, messages] of Object.entries(pollResult.inboxMessages)) {
729
+ for (const message of messages) {
730
+ // Handle nudge: no-op wake signal, just discard
731
+ if (message.type === 'nudge') {
732
+ log.debug('Nudge received (no-op)', {
733
+ sessionId: sessionId.substring(0, 8),
734
+ messageId: message.id,
735
+ });
736
+ await ackInboxMessage(sessionId, message);
737
+ continue;
738
+ }
739
+ const orchestrator = activeOrchestrators.get(sessionId);
740
+ // Handle stop signal: trigger graceful agent shutdown
741
+ if (message.type === 'stop') {
742
+ log.info('Stop signal received', {
743
+ sessionId: sessionId.substring(0, 8),
744
+ messageId: message.id,
745
+ });
746
+ if (orchestrator) {
747
+ const agent = orchestrator.getAgentBySession(sessionId);
748
+ if (agent) {
749
+ try {
750
+ await orchestrator.stopAgent(agent.issueId, false);
751
+ log.success('Agent stopped via inbox signal', {
752
+ sessionId: sessionId.substring(0, 8),
753
+ });
754
+ }
755
+ catch (error) {
756
+ log.error('Failed to stop agent', {
757
+ sessionId: sessionId.substring(0, 8),
758
+ error: error instanceof Error ? error.message : String(error),
759
+ });
760
+ }
761
+ }
762
+ }
763
+ await ackInboxMessage(sessionId, message);
764
+ continue;
765
+ }
766
+ // Handle directive and hook-result: inject as follow-up prompt
767
+ log.info('Processing inbox message', {
704
768
  sessionId: sessionId.substring(0, 8),
705
- promptId: prompt.id,
706
- promptLength: prompt.prompt.length,
707
- userName: prompt.userName,
769
+ messageId: message.id,
770
+ type: message.type,
771
+ lane: message.lane,
772
+ payloadLength: message.payload.length,
773
+ userName: message.userName,
708
774
  });
709
- const orchestrator = activeOrchestrators.get(sessionId);
710
775
  if (!orchestrator) {
711
776
  log.warn('No active orchestrator found for session', {
712
777
  sessionId: sessionId.substring(0, 8),
713
- promptId: prompt.id,
778
+ messageId: message.id,
714
779
  });
715
780
  continue;
716
781
  }
717
782
  const agent = orchestrator.getAgentBySession(sessionId);
718
783
  const providerSessionId = agent?.providerSessionId;
719
- log.info('Forwarding prompt to provider session', {
720
- sessionId: sessionId.substring(0, 8),
721
- promptId: prompt.id,
722
- hasProviderSession: !!providerSessionId,
723
- agentStatus: agent?.status,
724
- });
725
784
  try {
726
- const result = await orchestrator.forwardPrompt(prompt.issueId, sessionId, prompt.prompt, providerSessionId, agent?.workType);
785
+ const result = await orchestrator.forwardPrompt(agent?.issueId ?? '', sessionId, message.payload, providerSessionId, agent?.workType);
727
786
  if (result.forwarded) {
728
787
  log.success(result.injected
729
788
  ? 'Message injected into running session'
730
- : 'Prompt forwarded successfully', {
789
+ : 'Inbox message forwarded successfully', {
731
790
  sessionId: sessionId.substring(0, 8),
732
- promptId: prompt.id,
791
+ messageId: message.id,
792
+ type: message.type,
733
793
  injected: result.injected ?? false,
734
794
  resumed: result.resumed,
735
795
  newAgentPid: result.agent?.pid,
736
796
  });
737
- const claimResult = await apiRequest(`/api/sessions/${sessionId}/prompts`, {
738
- method: 'POST',
739
- body: JSON.stringify({ promptId: prompt.id }),
740
- });
741
- if (claimResult?.claimed) {
742
- log.debug('Prompt claimed', { promptId: prompt.id });
743
- }
744
- else {
745
- log.warn('Failed to claim prompt', { promptId: prompt.id });
746
- }
797
+ // ACK after successful delivery
798
+ await ackInboxMessage(sessionId, message);
747
799
  }
748
800
  else {
749
- log.error('Failed to forward prompt', {
801
+ log.error('Failed to forward inbox message', {
750
802
  sessionId: sessionId.substring(0, 8),
751
- promptId: prompt.id,
803
+ messageId: message.id,
804
+ type: message.type,
752
805
  reason: result.reason,
753
806
  error: result.error?.message,
754
807
  });
755
808
  }
756
809
  }
757
810
  catch (error) {
758
- log.error('Error forwarding prompt', {
811
+ log.error('Error forwarding inbox message', {
759
812
  sessionId: sessionId.substring(0, 8),
760
- promptId: prompt.id,
813
+ messageId: message.id,
761
814
  error: error instanceof Error ? error.message : String(error),
762
815
  });
763
816
  }
@@ -770,8 +823,9 @@ export async function runWorker(config, signal) {
770
823
  error: error instanceof Error ? error.message : String(error),
771
824
  });
772
825
  }
773
- // Wait before next poll
774
- await new Promise((resolve) => setTimeout(resolve, registration.pollInterval));
826
+ // Wait before next poll (with jitter to desynchronize workers)
827
+ const jitter = Math.floor(Math.random() * registration.pollInterval * 0.4);
828
+ await new Promise((resolve) => setTimeout(resolve, registration.pollInterval + jitter));
775
829
  }
776
830
  }
777
831
  finally {
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Status CLI
4
+ *
5
+ * Quick fleet status checks for terminal and scripting use.
6
+ *
7
+ * Usage:
8
+ * af-status One-line fleet summary (via Go binary in TTY)
9
+ * af-status --json JSON stats to stdout
10
+ * af-status --watch Auto-refresh every 3 seconds
11
+ * af-status --watch --interval 5s Custom refresh interval
12
+ * af-status | jq Pipe-friendly (auto-detects non-TTY)
13
+ *
14
+ * Environment (loaded from .env.local in CWD):
15
+ * WORKER_API_URL API base URL (default: http://localhost:3000)
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/status.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Status CLI
4
+ *
5
+ * Quick fleet status checks for terminal and scripting use.
6
+ *
7
+ * Usage:
8
+ * af-status One-line fleet summary (via Go binary in TTY)
9
+ * af-status --json JSON stats to stdout
10
+ * af-status --watch Auto-refresh every 3 seconds
11
+ * af-status --watch --interval 5s Custom refresh interval
12
+ * af-status | jq Pipe-friendly (auto-detects non-TTY)
13
+ *
14
+ * Environment (loaded from .env.local in CWD):
15
+ * WORKER_API_URL API base URL (default: http://localhost:3000)
16
+ */
17
+ import path from 'path';
18
+ import { config } from 'dotenv';
19
+ // Load environment variables from .env.local in CWD
20
+ config({ path: path.resolve(process.cwd(), '.env.local') });
21
+ import { runStatus, C } from './lib/status-runner.js';
22
+ // ---------------------------------------------------------------------------
23
+ // Usage
24
+ // ---------------------------------------------------------------------------
25
+ function printUsage() {
26
+ console.log(`
27
+ ${C.cyan}AgentFactory Status${C.reset} - Quick fleet status checks
28
+
29
+ ${C.yellow}Usage:${C.reset}
30
+ af-status [options]
31
+
32
+ ${C.yellow}Options:${C.reset}
33
+ --json Output raw JSON stats to stdout
34
+ --watch Auto-refresh status every 3 seconds
35
+ --interval <duration> Custom refresh interval (e.g., 1s, 5s) [default: 3s]
36
+ --url <url> API base URL [default: WORKER_API_URL or http://localhost:3000]
37
+ --help, -h Show this help message
38
+
39
+ ${C.yellow}Examples:${C.reset}
40
+ af-status One-line fleet summary
41
+ af-status --json JSON output
42
+ af-status --json | jq .agentsWorking Extract a field
43
+ af-status --watch Auto-refresh mode
44
+ af-status --watch --interval 1s Refresh every second
45
+
46
+ ${C.yellow}Environment:${C.reset}
47
+ WORKER_API_URL API base URL (default: http://localhost:3000)
48
+ `);
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Main
52
+ // ---------------------------------------------------------------------------
53
+ async function main() {
54
+ const args = process.argv.slice(2);
55
+ if (args.includes('--help') || args.includes('-h') || args.includes('help')) {
56
+ printUsage();
57
+ return;
58
+ }
59
+ const jsonMode = args.includes('--json');
60
+ const watchMode = args.includes('--watch');
61
+ // Parse --interval value
62
+ let interval;
63
+ const intervalIdx = args.indexOf('--interval');
64
+ if (intervalIdx !== -1 && intervalIdx + 1 < args.length) {
65
+ interval = args[intervalIdx + 1];
66
+ }
67
+ // Parse --url value
68
+ let url;
69
+ const urlIdx = args.indexOf('--url');
70
+ if (urlIdx !== -1 && urlIdx + 1 < args.length) {
71
+ url = args[urlIdx + 1];
72
+ }
73
+ await runStatus({ json: jsonMode, watch: watchMode, interval, url });
74
+ }
75
+ main().catch((error) => {
76
+ console.error('Error:', error instanceof Error ? error.message : error);
77
+ process.exit(1);
78
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renseiai/agentfactory-cli",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "type": "module",
5
5
  "description": "CLI tools for AgentFactory — local orchestrator, remote worker, queue admin",
6
6
  "author": "Rensei AI (https://rensei.ai)",
@@ -37,7 +37,8 @@
37
37
  "af-analyze-logs": "dist/src/analyze-logs.js",
38
38
  "af-linear": "dist/src/linear.js",
39
39
  "af-governor": "dist/src/governor.js",
40
- "af-sync-routes": "dist/src/sync-routes.js"
40
+ "af-sync-routes": "dist/src/sync-routes.js",
41
+ "af-status": "dist/src/status.js"
41
42
  },
42
43
  "main": "./dist/src/index.js",
43
44
  "module": "./dist/src/index.js",
@@ -92,6 +93,11 @@
92
93
  "types": "./dist/src/lib/sync-routes-runner.d.ts",
93
94
  "import": "./dist/src/lib/sync-routes-runner.js",
94
95
  "default": "./dist/src/lib/sync-routes-runner.js"
96
+ },
97
+ "./status": {
98
+ "types": "./dist/src/lib/status-runner.d.ts",
99
+ "import": "./dist/src/lib/status-runner.js",
100
+ "default": "./dist/src/lib/status-runner.js"
95
101
  }
96
102
  },
97
103
  "files": [
@@ -101,10 +107,10 @@
101
107
  ],
102
108
  "dependencies": {
103
109
  "dotenv": "^17.2.3",
104
- "@renseiai/agentfactory": "0.8.7",
105
- "@renseiai/plugin-linear": "0.8.7",
106
- "@renseiai/agentfactory-code-intelligence": "0.8.7",
107
- "@renseiai/agentfactory-server": "0.8.7"
110
+ "@renseiai/agentfactory": "0.8.9",
111
+ "@renseiai/plugin-linear": "0.8.9",
112
+ "@renseiai/agentfactory-server": "0.8.9",
113
+ "@renseiai/agentfactory-code-intelligence": "0.8.9"
108
114
  },
109
115
  "devDependencies": {
110
116
  "@types/node": "^22.5.4",