arcane-agents 1.1.0 → 1.2.1
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/README.md +30 -0
- package/dist/client/assets/index-BCnWppkv.js +83 -0
- package/dist/client/assets/{index-CWU29xaz.css → index-Di_KBFPW.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/server/bootstrap/serverContext.js +9 -2
- package/dist/server/server/bootstrapApp.js +9 -3
- package/dist/server/server/cli.js +122 -6
- package/dist/server/server/config/loadConfig.js +10 -2
- package/dist/server/server/http/routes/registerApiRoutes.js +10 -0
- package/dist/server/server/orchestrator/orchestratorService.js +29 -0
- package/dist/server/server/orchestrator/orchestratorService.test.js +58 -0
- package/dist/server/server/status/activityParser.js +1 -1
- package/dist/server/server/status/engine/signalContext.js +18 -6
- package/dist/server/server/status/engine/stateMachine/constants.js +5 -1
- package/dist/server/server/status/engine/stateMachine/decision.js +17 -0
- package/dist/server/server/status/engine/stateMachine/decision.test.js +63 -0
- package/dist/server/server/status/engine/stateMachine/helpers.js +4 -1
- package/dist/server/server/status/engine/stateMachine/helpers.test.js +6 -0
- package/dist/server/server/status/engine/stateMachine/idleBlockers.js +13 -0
- package/dist/server/server/status/engine/stateMachine/workingEvidence.js +23 -0
- package/dist/server/server/status/runtime/activityTextExtractors.js +40 -0
- package/dist/server/server/status/runtime/claudeSignals.js +114 -33
- package/dist/server/server/status/runtime/claudeSignals.test.js +23 -0
- package/dist/server/server/status/runtime/codexSignals.js +132 -0
- package/dist/server/server/status/runtime/codexSignals.test.js +47 -0
- package/dist/server/server/status/runtime/runtimeProcess.js +86 -0
- package/dist/server/server/status/runtime/sessionDetection.js +15 -0
- package/dist/server/server/status/runtimeSignals.js +8 -1
- package/dist/server/server/status/statusEvaluator.js +2 -1
- package/dist/server/server/status/statusMonitor.js +8 -1
- package/dist/server/server/status/statusMonitor.test.js +6 -0
- package/dist/server/server/status/statusPipeline.js +9 -1
- package/dist/server/server/ws/terminalBridge.js +11 -0
- package/package.json +1 -1
- package/dist/client/assets/index-HfXsReQG.js +0 -83
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findAgentRuntimeProcess = findAgentRuntimeProcess;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
7
|
+
const maxProcessTreeDepth = 5;
|
|
8
|
+
async function findAgentRuntimeProcess(panePid) {
|
|
9
|
+
return findAgentRuntimeProcessAtDepth(panePid, 0);
|
|
10
|
+
}
|
|
11
|
+
async function findAgentRuntimeProcessAtDepth(parentPid, depth) {
|
|
12
|
+
if (depth >= maxProcessTreeDepth) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await execFileAsync("pgrep", ["-P", String(parentPid)], {
|
|
17
|
+
maxBuffer: 1024 * 16
|
|
18
|
+
});
|
|
19
|
+
const childPids = stdout
|
|
20
|
+
.trim()
|
|
21
|
+
.split("\n")
|
|
22
|
+
.map((line) => Number.parseInt(line.trim(), 10))
|
|
23
|
+
.filter((pid) => Number.isFinite(pid) && pid > 0);
|
|
24
|
+
for (const childPid of childPids) {
|
|
25
|
+
const details = await describeProcess(childPid);
|
|
26
|
+
if (details) {
|
|
27
|
+
const runtime = classifyAgentRuntime(details.command, details.args);
|
|
28
|
+
if (runtime) {
|
|
29
|
+
return {
|
|
30
|
+
pid: childPid,
|
|
31
|
+
runtime,
|
|
32
|
+
command: details.command,
|
|
33
|
+
args: details.args
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const nestedMatch = await findAgentRuntimeProcessAtDepth(childPid, depth + 1);
|
|
38
|
+
if (nestedMatch) {
|
|
39
|
+
return nestedMatch;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
async function describeProcess(pid) {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execFileAsync("ps", ["-o", "comm=", "-o", "args=", "-p", String(pid)], {
|
|
51
|
+
maxBuffer: 1024 * 16
|
|
52
|
+
});
|
|
53
|
+
const line = stdout.trim();
|
|
54
|
+
if (!line) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const [command = "", ...argsParts] = line.split(/\s+/);
|
|
58
|
+
return {
|
|
59
|
+
command: command.trim(),
|
|
60
|
+
args: argsParts.join(" ").trim()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function classifyAgentRuntime(command, args) {
|
|
68
|
+
const commandLower = command.trim().toLowerCase();
|
|
69
|
+
const argsLower = args.trim().toLowerCase();
|
|
70
|
+
const commandAndArgs = `${commandLower} ${argsLower}`.trim();
|
|
71
|
+
if (commandLower === "claude" ||
|
|
72
|
+
commandAndArgs.includes("/claude") ||
|
|
73
|
+
/\bclaude(?:-code)?\b/.test(commandAndArgs)) {
|
|
74
|
+
return "claude";
|
|
75
|
+
}
|
|
76
|
+
if (commandLower === "opencode" || commandAndArgs.includes("opencode")) {
|
|
77
|
+
return "opencode";
|
|
78
|
+
}
|
|
79
|
+
if (commandLower === "codex" ||
|
|
80
|
+
commandAndArgs.includes("@openai/codex") ||
|
|
81
|
+
commandAndArgs.includes("/bin/codex") ||
|
|
82
|
+
/\bcodex\b/.test(commandAndArgs)) {
|
|
83
|
+
return "codex";
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
@@ -3,13 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.capturePaneLineCount = capturePaneLineCount;
|
|
4
4
|
exports.isLikelyClaudeSession = isLikelyClaudeSession;
|
|
5
5
|
exports.isLikelyOpenCodeSession = isLikelyOpenCodeSession;
|
|
6
|
+
exports.isLikelyCodexSession = isLikelyCodexSession;
|
|
6
7
|
const defaultCapturePaneLines = 35;
|
|
7
8
|
const claudeCapturePaneLines = 60;
|
|
8
9
|
const openCodeCapturePaneLines = 420;
|
|
10
|
+
const codexCapturePaneLines = 420;
|
|
9
11
|
function capturePaneLineCount(worker, commandLower) {
|
|
10
12
|
if (isLikelyOpenCodeSession(worker, commandLower)) {
|
|
11
13
|
return openCodeCapturePaneLines;
|
|
12
14
|
}
|
|
15
|
+
if (isLikelyCodexSession(worker, commandLower)) {
|
|
16
|
+
return codexCapturePaneLines;
|
|
17
|
+
}
|
|
13
18
|
if (isLikelyClaudeSession(worker, commandLower)) {
|
|
14
19
|
return claudeCapturePaneLines;
|
|
15
20
|
}
|
|
@@ -35,3 +40,13 @@ function isLikelyOpenCodeSession(worker, commandLower) {
|
|
|
35
40
|
}
|
|
36
41
|
return commandLower.includes("opencode");
|
|
37
42
|
}
|
|
43
|
+
function isLikelyCodexSession(worker, commandLower) {
|
|
44
|
+
if (worker.runtimeId.toLowerCase().includes("codex")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const runtimeBinary = worker.command[0]?.toLowerCase() ?? "";
|
|
48
|
+
if (runtimeBinary.includes("codex")) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return commandLower.includes("codex");
|
|
52
|
+
}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractRuntimeActivityText = exports.hasWaitingActivityText = exports.hasActiveWorkActivityText = exports.preferOpenCodeSpecificActivityText = exports.hasOpenCodeActiveSignal = exports.hasOpenCodePromptSignal = exports.detectOpenCodeSignals = exports.isGenericClaudeProgressLabel = exports.hasClaudeLiveProgressSignal = exports.extractClaudeActiveTask = exports.isLikelyOpenCodeSession = exports.isLikelyClaudeSession = exports.capturePaneLineCount = void 0;
|
|
3
|
+
exports.extractRuntimeActivityText = exports.hasWaitingActivityText = exports.hasActiveWorkActivityText = exports.hasCodexActiveSignal = exports.hasCodexPromptSignal = exports.detectCodexSignals = exports.preferOpenCodeSpecificActivityText = exports.hasOpenCodeActiveSignal = exports.hasOpenCodePromptSignal = exports.detectOpenCodeSignals = exports.isGenericClaudeProgressLabel = exports.hasClaudePromptSignal = exports.hasClaudeLiveProgressSignal = exports.extractClaudeActiveTask = exports.detectClaudeSignals = exports.isLikelyCodexSession = exports.isLikelyOpenCodeSession = exports.isLikelyClaudeSession = exports.capturePaneLineCount = void 0;
|
|
4
4
|
var sessionDetection_1 = require("./runtime/sessionDetection");
|
|
5
5
|
Object.defineProperty(exports, "capturePaneLineCount", { enumerable: true, get: function () { return sessionDetection_1.capturePaneLineCount; } });
|
|
6
6
|
Object.defineProperty(exports, "isLikelyClaudeSession", { enumerable: true, get: function () { return sessionDetection_1.isLikelyClaudeSession; } });
|
|
7
7
|
Object.defineProperty(exports, "isLikelyOpenCodeSession", { enumerable: true, get: function () { return sessionDetection_1.isLikelyOpenCodeSession; } });
|
|
8
|
+
Object.defineProperty(exports, "isLikelyCodexSession", { enumerable: true, get: function () { return sessionDetection_1.isLikelyCodexSession; } });
|
|
8
9
|
var claudeSignals_1 = require("./runtime/claudeSignals");
|
|
10
|
+
Object.defineProperty(exports, "detectClaudeSignals", { enumerable: true, get: function () { return claudeSignals_1.detectClaudeSignals; } });
|
|
9
11
|
Object.defineProperty(exports, "extractClaudeActiveTask", { enumerable: true, get: function () { return claudeSignals_1.extractClaudeActiveTask; } });
|
|
10
12
|
Object.defineProperty(exports, "hasClaudeLiveProgressSignal", { enumerable: true, get: function () { return claudeSignals_1.hasClaudeLiveProgressSignal; } });
|
|
13
|
+
Object.defineProperty(exports, "hasClaudePromptSignal", { enumerable: true, get: function () { return claudeSignals_1.hasClaudePromptSignal; } });
|
|
11
14
|
Object.defineProperty(exports, "isGenericClaudeProgressLabel", { enumerable: true, get: function () { return claudeSignals_1.isGenericClaudeProgressLabel; } });
|
|
12
15
|
var openCodeSignals_1 = require("./runtime/openCodeSignals");
|
|
13
16
|
Object.defineProperty(exports, "detectOpenCodeSignals", { enumerable: true, get: function () { return openCodeSignals_1.detectOpenCodeSignals; } });
|
|
14
17
|
Object.defineProperty(exports, "hasOpenCodePromptSignal", { enumerable: true, get: function () { return openCodeSignals_1.hasOpenCodePromptSignal; } });
|
|
15
18
|
Object.defineProperty(exports, "hasOpenCodeActiveSignal", { enumerable: true, get: function () { return openCodeSignals_1.hasOpenCodeActiveSignal; } });
|
|
16
19
|
Object.defineProperty(exports, "preferOpenCodeSpecificActivityText", { enumerable: true, get: function () { return openCodeSignals_1.preferOpenCodeSpecificActivityText; } });
|
|
20
|
+
var codexSignals_1 = require("./runtime/codexSignals");
|
|
21
|
+
Object.defineProperty(exports, "detectCodexSignals", { enumerable: true, get: function () { return codexSignals_1.detectCodexSignals; } });
|
|
22
|
+
Object.defineProperty(exports, "hasCodexPromptSignal", { enumerable: true, get: function () { return codexSignals_1.hasCodexPromptSignal; } });
|
|
23
|
+
Object.defineProperty(exports, "hasCodexActiveSignal", { enumerable: true, get: function () { return codexSignals_1.hasCodexActiveSignal; } });
|
|
17
24
|
var textSignals_1 = require("./runtime/textSignals");
|
|
18
25
|
Object.defineProperty(exports, "hasActiveWorkActivityText", { enumerable: true, get: function () { return textSignals_1.hasActiveWorkActivityText; } });
|
|
19
26
|
Object.defineProperty(exports, "hasWaitingActivityText", { enumerable: true, get: function () { return textSignals_1.hasWaitingActivityText; } });
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.evaluateWorkerStatus = evaluateWorkerStatus;
|
|
4
4
|
const signalContext_1 = require("./engine/signalContext");
|
|
5
5
|
const stateMachine_1 = require("./engine/stateMachine");
|
|
6
|
-
function evaluateWorkerStatus({ worker, currentCommand, output, observation, transcriptSnapshot, interactiveCommands }) {
|
|
6
|
+
function evaluateWorkerStatus({ worker, currentCommand, output, observation, transcriptSnapshot, runtimeProcess, interactiveCommands }) {
|
|
7
7
|
const context = (0, signalContext_1.buildWorkerStatusSignalContext)({
|
|
8
8
|
worker,
|
|
9
9
|
currentCommand,
|
|
10
10
|
output,
|
|
11
11
|
observation,
|
|
12
12
|
transcriptSnapshot,
|
|
13
|
+
runtimeProcess,
|
|
13
14
|
nowMs: Date.now(),
|
|
14
15
|
interactiveCommands
|
|
15
16
|
});
|
|
@@ -10,10 +10,15 @@ const defaultDecisionFacts = {
|
|
|
10
10
|
workerAgeMs: 0,
|
|
11
11
|
isClaudeSession: false,
|
|
12
12
|
isOpenCodeSession: false,
|
|
13
|
+
isCodexSession: false,
|
|
14
|
+
hasClaudePromptSignal: false,
|
|
13
15
|
hasOpenCodePromptSignal: false,
|
|
14
16
|
hasOpenCodeActiveSignal: false,
|
|
17
|
+
hasCodexPromptSignal: false,
|
|
18
|
+
hasCodexActiveSignal: false,
|
|
15
19
|
hasClaudeProgressSignal: false,
|
|
16
20
|
hasActiveClaudeTask: false,
|
|
21
|
+
hasActiveRuntimeProcess: false,
|
|
17
22
|
hasRuntimeActivityText: false,
|
|
18
23
|
hasParsedStrongSignal: false,
|
|
19
24
|
hasParsedNeedsInput: false,
|
|
@@ -305,7 +310,9 @@ class StatusMonitor {
|
|
|
305
310
|
`outQuiet=${Math.round(evaluation.facts.outputQuietForMs)}ms ` +
|
|
306
311
|
`cmdQuiet=${Math.round(evaluation.facts.commandQuietForMs)}ms ` +
|
|
307
312
|
`claude=${evaluation.facts.isClaudeSession ? 1 : 0} ` +
|
|
308
|
-
`opencode=${evaluation.facts.isOpenCodeSession ? 1 : 0}
|
|
313
|
+
`opencode=${evaluation.facts.isOpenCodeSession ? 1 : 0} ` +
|
|
314
|
+
`codex=${evaluation.facts.isCodexSession ? 1 : 0} ` +
|
|
315
|
+
`runtimeProc=${evaluation.facts.hasActiveRuntimeProcess ? 1 : 0}`;
|
|
309
316
|
console.log(`[arcane-agents][status] ${worker.displayName ?? worker.name} ${fromTo} (${Math.round(evaluation.confidence * 100)}%)${activityText} reasons=[${reasonText}] ${traceFacts}`);
|
|
310
317
|
}
|
|
311
318
|
recordStatusTransition(worker, evaluation) {
|
|
@@ -16,10 +16,15 @@ const defaultFacts = {
|
|
|
16
16
|
workerAgeMs: 0,
|
|
17
17
|
isClaudeSession: true,
|
|
18
18
|
isOpenCodeSession: false,
|
|
19
|
+
isCodexSession: false,
|
|
20
|
+
hasClaudePromptSignal: false,
|
|
19
21
|
hasOpenCodePromptSignal: false,
|
|
20
22
|
hasOpenCodeActiveSignal: false,
|
|
23
|
+
hasCodexPromptSignal: false,
|
|
24
|
+
hasCodexActiveSignal: false,
|
|
21
25
|
hasClaudeProgressSignal: false,
|
|
22
26
|
hasActiveClaudeTask: false,
|
|
27
|
+
hasActiveRuntimeProcess: false,
|
|
23
28
|
hasRuntimeActivityText: false,
|
|
24
29
|
hasParsedStrongSignal: false,
|
|
25
30
|
hasParsedNeedsInput: false,
|
|
@@ -90,6 +95,7 @@ function createSignals() {
|
|
|
90
95
|
lastOutputChangeAtMs: Date.now()
|
|
91
96
|
},
|
|
92
97
|
transcriptSnapshot: undefined,
|
|
98
|
+
runtimeProcess: undefined,
|
|
93
99
|
interactiveCommands: new Set()
|
|
94
100
|
};
|
|
95
101
|
}
|
|
@@ -4,6 +4,7 @@ exports.collectWorkerStatusSignals = collectWorkerStatusSignals;
|
|
|
4
4
|
exports.evaluateWorkerStatusSignals = evaluateWorkerStatusSignals;
|
|
5
5
|
exports.normalizeWorkerStatusEvaluation = normalizeWorkerStatusEvaluation;
|
|
6
6
|
const runtimeSignals_1 = require("./runtimeSignals");
|
|
7
|
+
const runtimeProcess_1 = require("./runtime/runtimeProcess");
|
|
7
8
|
const statusEvaluator_1 = require("./statusEvaluator");
|
|
8
9
|
const paneObservation_1 = require("./paneObservation");
|
|
9
10
|
async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claudeTranscript, interactiveCommands }) {
|
|
@@ -11,8 +12,13 @@ async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claud
|
|
|
11
12
|
if (paneState.isDead) {
|
|
12
13
|
return undefined;
|
|
13
14
|
}
|
|
15
|
+
const currentCommandLower = paneState.currentCommand.toLowerCase();
|
|
16
|
+
const runtimeProcess = paneState.panePid && (currentCommandLower === "bash" || currentCommandLower === "zsh" || currentCommandLower === "sh")
|
|
17
|
+
? await (0, runtimeProcess_1.findAgentRuntimeProcess)(paneState.panePid)
|
|
18
|
+
: undefined;
|
|
19
|
+
const captureCommand = runtimeProcess?.runtime ?? currentCommandLower;
|
|
14
20
|
const [output, transcriptSnapshot] = await Promise.all([
|
|
15
|
-
tmux.capturePane(worker.tmuxRef, (0, runtimeSignals_1.capturePaneLineCount)(worker,
|
|
21
|
+
tmux.capturePane(worker.tmuxRef, (0, runtimeSignals_1.capturePaneLineCount)(worker, captureCommand)),
|
|
16
22
|
claudeTranscript.poll(worker, paneState.currentCommand, paneState.currentPath, paneState.panePid)
|
|
17
23
|
]);
|
|
18
24
|
const observation = (0, paneObservation_1.observePane)(paneObservation, worker.id, paneState.currentCommand, output);
|
|
@@ -21,6 +27,7 @@ async function collectWorkerStatusSignals({ worker, tmux, paneObservation, claud
|
|
|
21
27
|
output,
|
|
22
28
|
observation,
|
|
23
29
|
transcriptSnapshot,
|
|
30
|
+
runtimeProcess,
|
|
24
31
|
interactiveCommands
|
|
25
32
|
};
|
|
26
33
|
}
|
|
@@ -31,6 +38,7 @@ function evaluateWorkerStatusSignals(worker, signals) {
|
|
|
31
38
|
output: signals.output,
|
|
32
39
|
observation: signals.observation,
|
|
33
40
|
transcriptSnapshot: signals.transcriptSnapshot,
|
|
41
|
+
runtimeProcess: signals.runtimeProcess,
|
|
34
42
|
interactiveCommands: signals.interactiveCommands
|
|
35
43
|
});
|
|
36
44
|
}
|
|
@@ -38,6 +38,7 @@ const pty = __importStar(require("node-pty"));
|
|
|
38
38
|
class TerminalBridge {
|
|
39
39
|
workers;
|
|
40
40
|
options;
|
|
41
|
+
lastOutputActivityAtByWorker = new Map();
|
|
41
42
|
constructor(workers, options = {}) {
|
|
42
43
|
this.workers = workers;
|
|
43
44
|
this.options = options;
|
|
@@ -79,6 +80,7 @@ class TerminalBridge {
|
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
terminal.onData((chunk) => {
|
|
83
|
+
this.handleTerminalOutput(worker.id);
|
|
82
84
|
if (socket.readyState === socket.OPEN) {
|
|
83
85
|
socket.send(chunk);
|
|
84
86
|
}
|
|
@@ -109,6 +111,15 @@ class TerminalBridge {
|
|
|
109
111
|
socket.on("close", cleanup);
|
|
110
112
|
socket.on("error", cleanup);
|
|
111
113
|
}
|
|
114
|
+
handleTerminalOutput(workerId) {
|
|
115
|
+
const nowMs = Date.now();
|
|
116
|
+
const lastOutputActivityAtMs = this.lastOutputActivityAtByWorker.get(workerId) ?? 0;
|
|
117
|
+
if (nowMs - lastOutputActivityAtMs < 120) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.lastOutputActivityAtByWorker.set(workerId, nowMs);
|
|
121
|
+
this.options.onTerminalOutput?.(workerId);
|
|
122
|
+
}
|
|
112
123
|
}
|
|
113
124
|
exports.TerminalBridge = TerminalBridge;
|
|
114
125
|
function parseResizeMessage(value) {
|