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.
Files changed (35) hide show
  1. package/README.md +30 -0
  2. package/dist/client/assets/index-BCnWppkv.js +83 -0
  3. package/dist/client/assets/{index-CWU29xaz.css → index-Di_KBFPW.css} +1 -1
  4. package/dist/client/index.html +2 -2
  5. package/dist/server/server/bootstrap/serverContext.js +9 -2
  6. package/dist/server/server/bootstrapApp.js +9 -3
  7. package/dist/server/server/cli.js +122 -6
  8. package/dist/server/server/config/loadConfig.js +10 -2
  9. package/dist/server/server/http/routes/registerApiRoutes.js +10 -0
  10. package/dist/server/server/orchestrator/orchestratorService.js +29 -0
  11. package/dist/server/server/orchestrator/orchestratorService.test.js +58 -0
  12. package/dist/server/server/status/activityParser.js +1 -1
  13. package/dist/server/server/status/engine/signalContext.js +18 -6
  14. package/dist/server/server/status/engine/stateMachine/constants.js +5 -1
  15. package/dist/server/server/status/engine/stateMachine/decision.js +17 -0
  16. package/dist/server/server/status/engine/stateMachine/decision.test.js +63 -0
  17. package/dist/server/server/status/engine/stateMachine/helpers.js +4 -1
  18. package/dist/server/server/status/engine/stateMachine/helpers.test.js +6 -0
  19. package/dist/server/server/status/engine/stateMachine/idleBlockers.js +13 -0
  20. package/dist/server/server/status/engine/stateMachine/workingEvidence.js +23 -0
  21. package/dist/server/server/status/runtime/activityTextExtractors.js +40 -0
  22. package/dist/server/server/status/runtime/claudeSignals.js +114 -33
  23. package/dist/server/server/status/runtime/claudeSignals.test.js +23 -0
  24. package/dist/server/server/status/runtime/codexSignals.js +132 -0
  25. package/dist/server/server/status/runtime/codexSignals.test.js +47 -0
  26. package/dist/server/server/status/runtime/runtimeProcess.js +86 -0
  27. package/dist/server/server/status/runtime/sessionDetection.js +15 -0
  28. package/dist/server/server/status/runtimeSignals.js +8 -1
  29. package/dist/server/server/status/statusEvaluator.js +2 -1
  30. package/dist/server/server/status/statusMonitor.js +8 -1
  31. package/dist/server/server/status/statusMonitor.test.js +6 -0
  32. package/dist/server/server/status/statusPipeline.js +9 -1
  33. package/dist/server/server/ws/terminalBridge.js +11 -0
  34. package/package.json +1 -1
  35. 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, paneState.currentCommand.toLowerCase())),
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-agents",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Local-first visual control room for tmux-backed coding agents",
5
5
  "bin": {
6
6
  "arcane-agents": "dist/server/server/cli.js"