@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.
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/lib/agent-runner.d.ts.map +1 -1
- package/dist/src/lib/agent-runner.js +17 -6
- package/dist/src/lib/status-runner.d.ts +27 -0
- package/dist/src/lib/status-runner.d.ts.map +1 -0
- package/dist/src/lib/status-runner.js +101 -0
- package/dist/src/lib/worker-runner.d.ts.map +1 -1
- package/dist/src/lib/worker-runner.js +101 -47
- package/dist/src/status.d.ts +18 -0
- package/dist/src/status.d.ts.map +1 -0
- package/dist/src/status.js +78 -0
- package/package.json +12 -6
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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;
|
|
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,
|
|
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
|
|
118
|
-
if (
|
|
119
|
-
console.
|
|
117
|
+
const agentId = session.agentId;
|
|
118
|
+
if (!agentId) {
|
|
119
|
+
console.error(`${C.red}Session has no agentId — cannot publish to inbox${C.reset}`);
|
|
120
|
+
await disconnectRedis();
|
|
121
|
+
return;
|
|
120
122
|
}
|
|
121
|
-
|
|
122
|
-
|
|
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;
|
|
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.
|
|
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: [],
|
|
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: [],
|
|
284
|
+
return { work: [], inboxMessages: {}, hasInboxMessages: false };
|
|
285
285
|
}
|
|
286
286
|
if (!result.data) {
|
|
287
|
-
return { work: [],
|
|
287
|
+
return { work: [], inboxMessages: {}, hasInboxMessages: false };
|
|
288
288
|
}
|
|
289
289
|
const pollData = result.data;
|
|
290
|
-
if (pollData.
|
|
291
|
-
const
|
|
292
|
-
log.info('Received
|
|
293
|
-
sessionCount: Object.keys(pollData.
|
|
294
|
-
|
|
295
|
-
sessions: Object.entries(pollData.
|
|
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
|
-
|
|
298
|
-
|
|
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
|
|
700
|
-
if (pollResult.
|
|
701
|
-
for (const [sessionId,
|
|
702
|
-
for (const
|
|
703
|
-
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
: '
|
|
789
|
+
: 'Inbox message forwarded successfully', {
|
|
731
790
|
sessionId: sessionId.substring(0, 8),
|
|
732
|
-
|
|
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
|
-
|
|
738
|
-
|
|
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
|
|
801
|
+
log.error('Failed to forward inbox message', {
|
|
750
802
|
sessionId: sessionId.substring(0, 8),
|
|
751
|
-
|
|
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
|
|
811
|
+
log.error('Error forwarding inbox message', {
|
|
759
812
|
sessionId: sessionId.substring(0, 8),
|
|
760
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
105
|
-
"@renseiai/plugin-linear": "0.8.
|
|
106
|
-
"@renseiai/agentfactory-
|
|
107
|
-
"@renseiai/agentfactory-
|
|
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",
|