arcane-agents 1.0.2 → 1.2.0
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-CWU29xaz.css +32 -0
- package/dist/client/assets/index-DNXJVqF0.js +83 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/bootstrap/serverContext.js +6 -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/requestParsers.js +13 -0
- package/dist/server/server/orchestrator/spawn/resolveSpawnPlan.js +3 -2
- package/dist/server/server/status/engine/stateMachine/constants.js +3 -1
- package/dist/server/server/status/engine/stateMachine/decision.test.js +13 -0
- package/dist/server/server/status/engine/stateMachine/idleBlockers.js +13 -0
- package/dist/server/server/tmux/tmuxAdapter.js +8 -9
- package/package.json +1 -1
- package/dist/client/assets/index-CyA5FKrE.js +0 -80
- package/dist/client/assets/index-TxsheMVB.css +0 -32
package/dist/client/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Arcane Agents</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DNXJVqF0.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CWU29xaz.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -13,12 +13,16 @@ const statusMonitor_1 = require("../status/statusMonitor");
|
|
|
13
13
|
const tmuxAdapter_1 = require("../tmux/tmuxAdapter");
|
|
14
14
|
const realtimeHub_1 = require("../ws/realtimeHub");
|
|
15
15
|
const terminalBridge_1 = require("../ws/terminalBridge");
|
|
16
|
-
async function createServerContext() {
|
|
17
|
-
const paths = (0, loadConfig_1.getArcaneAgentsPaths)();
|
|
16
|
+
async function createServerContext(sessionName) {
|
|
17
|
+
const paths = (0, loadConfig_1.getArcaneAgentsPaths)(sessionName);
|
|
18
18
|
node_fs_1.default.mkdirSync(paths.configDir, { recursive: true });
|
|
19
19
|
node_fs_1.default.mkdirSync(paths.stateDir, { recursive: true });
|
|
20
20
|
node_fs_1.default.mkdirSync(paths.cacheDir, { recursive: true });
|
|
21
21
|
const baseConfig = (0, loadConfig_1.loadResolvedConfig)(paths);
|
|
22
|
+
if ((0, loadConfig_1.isNonDefaultSession)(sessionName)) {
|
|
23
|
+
const baseTmuxName = baseConfig.backend.tmux.sessionName;
|
|
24
|
+
baseConfig.backend.tmux.sessionName = `${baseTmuxName}-${sessionName}`;
|
|
25
|
+
}
|
|
22
26
|
const discoveryService = new discovery_1.DiscoveryService();
|
|
23
27
|
const initialDiscovery = await discoveryService.discover(baseConfig);
|
|
24
28
|
for (const warning of initialDiscovery.warnings) {
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.bootstrap = bootstrap;
|
|
7
7
|
const node_http_1 = __importDefault(require("node:http"));
|
|
8
8
|
const httpApp_1 = require("./bootstrap/httpApp");
|
|
9
|
+
const loadConfig_1 = require("./config/loadConfig");
|
|
9
10
|
const serverContext_1 = require("./bootstrap/serverContext");
|
|
10
11
|
const shutdown_1 = require("./bootstrap/shutdown");
|
|
11
12
|
const websocketUpgrade_1 = require("./bootstrap/websocketUpgrade");
|
|
@@ -61,10 +62,15 @@ function renderStartupBanner() {
|
|
|
61
62
|
})
|
|
62
63
|
.join("\n");
|
|
63
64
|
}
|
|
64
|
-
async function bootstrap() {
|
|
65
|
+
async function bootstrap(sessionName) {
|
|
65
66
|
console.log(renderStartupBanner());
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if ((0, loadConfig_1.isNonDefaultSession)(sessionName)) {
|
|
68
|
+
console.log(`[arcane-agents] launching Arcane Agents (session: ${sessionName})...`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log("[arcane-agents] launching Arcane Agents...");
|
|
72
|
+
}
|
|
73
|
+
const context = await (0, serverContext_1.createServerContext)(sessionName);
|
|
68
74
|
context.statusMonitor.start();
|
|
69
75
|
const app = (0, httpApp_1.createHttpApp)(context);
|
|
70
76
|
const server = node_http_1.default.createServer(app);
|
|
@@ -7,13 +7,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
10
11
|
const bootstrapApp_1 = require("./bootstrapApp");
|
|
11
12
|
const loadConfig_1 = require("./config/loadConfig");
|
|
12
13
|
const appRoot_1 = require("./utils/appRoot");
|
|
13
14
|
const path_1 = require("./utils/path");
|
|
15
|
+
function extractSessionFlag(args) {
|
|
16
|
+
const remaining = [...args];
|
|
17
|
+
let sessionName;
|
|
18
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
19
|
+
if (remaining[i] === "--session" || remaining[i] === "-s") {
|
|
20
|
+
const value = remaining[i + 1];
|
|
21
|
+
if (!value || value.startsWith("-")) {
|
|
22
|
+
console.error("[arcane-agents] --session requires a name argument.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
sessionName = value;
|
|
26
|
+
remaining.splice(i, 2);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
const eqMatch = remaining[i].match(/^(?:--session|-s)=(.+)$/);
|
|
30
|
+
if (eqMatch) {
|
|
31
|
+
sessionName = eqMatch[1];
|
|
32
|
+
remaining.splice(i, 1);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (sessionName !== undefined && !/^[a-zA-Z0-9_-]+$/.test(sessionName)) {
|
|
37
|
+
console.error("[arcane-agents] session name must only contain letters, digits, hyphens, and underscores.");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
return { sessionName, remainingArgs: remaining };
|
|
41
|
+
}
|
|
14
42
|
async function runCli() {
|
|
15
43
|
(0, appRoot_1.setAppRoot)((0, appRoot_1.resolveAppRoot)());
|
|
16
|
-
const args = process.argv.slice(2);
|
|
44
|
+
const { sessionName, remainingArgs: args } = extractSessionFlag(process.argv.slice(2));
|
|
17
45
|
const firstArg = args[0];
|
|
18
46
|
if (firstArg === "--help" || firstArg === "-h") {
|
|
19
47
|
printHelp();
|
|
@@ -26,13 +54,15 @@ async function runCli() {
|
|
|
26
54
|
const [command = "start", ...commandArgs] = args;
|
|
27
55
|
switch (command) {
|
|
28
56
|
case "start":
|
|
29
|
-
return runStart();
|
|
57
|
+
return runStart(sessionName);
|
|
30
58
|
case "init":
|
|
31
59
|
return runInit(commandArgs);
|
|
32
60
|
case "config":
|
|
33
61
|
return runConfig(commandArgs);
|
|
34
62
|
case "doctor":
|
|
35
63
|
return runDoctor();
|
|
64
|
+
case "sessions":
|
|
65
|
+
return runSessions(commandArgs);
|
|
36
66
|
case "help":
|
|
37
67
|
printHelp();
|
|
38
68
|
return 0;
|
|
@@ -45,11 +75,11 @@ async function runCli() {
|
|
|
45
75
|
return 1;
|
|
46
76
|
}
|
|
47
77
|
}
|
|
48
|
-
async function runStart() {
|
|
78
|
+
async function runStart(sessionName) {
|
|
49
79
|
if (!process.env.NODE_ENV) {
|
|
50
80
|
process.env.NODE_ENV = "production";
|
|
51
81
|
}
|
|
52
|
-
const paths = (0, loadConfig_1.getArcaneAgentsPaths)();
|
|
82
|
+
const paths = (0, loadConfig_1.getArcaneAgentsPaths)(sessionName);
|
|
53
83
|
let configResult;
|
|
54
84
|
try {
|
|
55
85
|
configResult = ensureStarterConfig(paths);
|
|
@@ -63,7 +93,7 @@ async function runStart() {
|
|
|
63
93
|
console.log(`[arcane-agents] no config found; wrote starter config to ${paths.configPath}`);
|
|
64
94
|
console.log("[arcane-agents] next: edit it with 'arcane-agents config edit'.");
|
|
65
95
|
}
|
|
66
|
-
await (0, bootstrapApp_1.bootstrap)();
|
|
96
|
+
await (0, bootstrapApp_1.bootstrap)(sessionName);
|
|
67
97
|
return 0;
|
|
68
98
|
}
|
|
69
99
|
function runInit(args) {
|
|
@@ -235,6 +265,85 @@ Commands:
|
|
|
235
265
|
help Show this config help message
|
|
236
266
|
`);
|
|
237
267
|
}
|
|
268
|
+
async function runSessions(args) {
|
|
269
|
+
const [subcommand = "list", ...subcommandArgs] = args;
|
|
270
|
+
switch (subcommand) {
|
|
271
|
+
case "list":
|
|
272
|
+
break;
|
|
273
|
+
case "delete":
|
|
274
|
+
return runSessionsDelete(subcommandArgs);
|
|
275
|
+
default:
|
|
276
|
+
console.error(`[arcane-agents] unknown sessions command '${subcommand}'.`);
|
|
277
|
+
console.log("Usage: arcane-agents sessions [list|delete <name>]");
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
const defaultPaths = (0, loadConfig_1.getArcaneAgentsPaths)();
|
|
281
|
+
const sessionsDir = node_path_1.default.join(defaultPaths.stateDir, "sessions");
|
|
282
|
+
const defaultDbPath = defaultPaths.dbPath;
|
|
283
|
+
const sessions = [];
|
|
284
|
+
if (node_fs_1.default.existsSync(defaultDbPath)) {
|
|
285
|
+
sessions.push("default");
|
|
286
|
+
}
|
|
287
|
+
if (node_fs_1.default.existsSync(sessionsDir)) {
|
|
288
|
+
try {
|
|
289
|
+
const entries = node_fs_1.default.readdirSync(sessionsDir, { withFileTypes: true });
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
if (entry.isDirectory()) {
|
|
292
|
+
const dbPath = node_path_1.default.join(sessionsDir, entry.name, "arcane-agents.db");
|
|
293
|
+
if (node_fs_1.default.existsSync(dbPath)) {
|
|
294
|
+
sessions.push(entry.name);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// no-op
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (sessions.length === 0) {
|
|
304
|
+
console.log("[arcane-agents] no sessions found.");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.log("[arcane-agents] sessions:");
|
|
308
|
+
for (const session of sessions) {
|
|
309
|
+
console.log(` ${session}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
async function runSessionsDelete(args) {
|
|
315
|
+
const name = args[0];
|
|
316
|
+
if (!name) {
|
|
317
|
+
console.error("[arcane-agents] usage: arcane-agents sessions delete <name>");
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
if (name === "default") {
|
|
321
|
+
console.error("[arcane-agents] cannot delete the default session.");
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
const sessionDir = (0, loadConfig_1.getArcaneAgentsPaths)(name).stateDir;
|
|
325
|
+
if (!node_fs_1.default.existsSync(sessionDir)) {
|
|
326
|
+
console.error(`[arcane-agents] session '${name}' not found.`);
|
|
327
|
+
return 1;
|
|
328
|
+
}
|
|
329
|
+
const answer = await promptConfirm(`Delete session '${name}' and all its data (${sessionDir})? [y/N] `);
|
|
330
|
+
if (!answer) {
|
|
331
|
+
console.log("[arcane-agents] aborted.");
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
node_fs_1.default.rmSync(sessionDir, { recursive: true, force: true });
|
|
335
|
+
console.log(`[arcane-agents] deleted session '${name}'.`);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
function promptConfirm(question) {
|
|
339
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
340
|
+
return new Promise((resolve) => {
|
|
341
|
+
rl.question(question, (answer) => {
|
|
342
|
+
rl.close();
|
|
343
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
238
347
|
function runDoctor() {
|
|
239
348
|
const checks = [];
|
|
240
349
|
const nodeVersion = process.versions.node;
|
|
@@ -357,9 +466,10 @@ function printHelp() {
|
|
|
357
466
|
console.log(`Arcane Agents CLI
|
|
358
467
|
|
|
359
468
|
Usage:
|
|
360
|
-
arcane-agents [start]
|
|
469
|
+
arcane-agents [start] [--session <name>]
|
|
361
470
|
arcane-agents init [--force]
|
|
362
471
|
arcane-agents config [path|show|edit]
|
|
472
|
+
arcane-agents sessions [list|delete <name>]
|
|
363
473
|
arcane-agents doctor
|
|
364
474
|
arcane-agents --help
|
|
365
475
|
arcane-agents --version
|
|
@@ -368,10 +478,16 @@ Commands:
|
|
|
368
478
|
start Start the Arcane Agents server
|
|
369
479
|
init Write ~/.config/arcane-agents/config.yaml from config.example.yaml
|
|
370
480
|
config Print, show, or edit config files
|
|
481
|
+
sessions List or delete named sessions
|
|
371
482
|
doctor Check dependencies and runtime command availability
|
|
372
483
|
help Show this help message
|
|
373
484
|
version Print CLI version
|
|
374
485
|
|
|
486
|
+
Options:
|
|
487
|
+
--session <name>, -s <name>
|
|
488
|
+
Run with a named session (separate DB and tmux session).
|
|
489
|
+
Default session uses the standard paths for backwards compatibility.
|
|
490
|
+
|
|
375
491
|
Config paths:
|
|
376
492
|
primary: ${paths.configPath}
|
|
377
493
|
local override: ${paths.localOverridePath}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isNonDefaultSession = isNonDefaultSession;
|
|
6
7
|
exports.getArcaneAgentsPaths = getArcaneAgentsPaths;
|
|
7
8
|
exports.loadResolvedConfig = loadResolvedConfig;
|
|
8
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -10,9 +11,16 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
10
11
|
const yaml_1 = __importDefault(require("yaml"));
|
|
11
12
|
const schema_1 = require("./schema");
|
|
12
13
|
const path_1 = require("../utils/path");
|
|
13
|
-
function
|
|
14
|
+
function isNonDefaultSession(sessionName) {
|
|
15
|
+
return sessionName !== undefined && sessionName !== "default";
|
|
16
|
+
}
|
|
17
|
+
function getArcaneAgentsPaths(sessionName) {
|
|
14
18
|
const configDir = (0, path_1.resolveUserPath)("~/.config/arcane-agents");
|
|
15
|
-
const
|
|
19
|
+
const baseStateDir = (0, path_1.resolveUserPath)("~/.local/state/arcane-agents");
|
|
20
|
+
const isNamedSession = sessionName !== undefined && sessionName !== "default";
|
|
21
|
+
const stateDir = isNamedSession
|
|
22
|
+
? node_path_1.default.join(baseStateDir, "sessions", sessionName)
|
|
23
|
+
: baseStateDir;
|
|
16
24
|
return {
|
|
17
25
|
configDir,
|
|
18
26
|
configPath: node_path_1.default.join(configDir, "config.yaml"),
|
|
@@ -9,12 +9,14 @@ function parseSpawnInput(body) {
|
|
|
9
9
|
}
|
|
10
10
|
const record = body;
|
|
11
11
|
const spawnNearWorkerIds = parseSpawnNearWorkerIds(record);
|
|
12
|
+
const displayName = parseDisplayName(record);
|
|
12
13
|
if (typeof record.shortcutIndex !== "undefined") {
|
|
13
14
|
if (typeof record.shortcutIndex !== "number" || !Number.isInteger(record.shortcutIndex) || record.shortcutIndex < 0) {
|
|
14
15
|
throw (0, appError_1.validationError)("shortcutIndex must be a non-negative integer.", "spawn_invalid_shortcut_index");
|
|
15
16
|
}
|
|
16
17
|
return {
|
|
17
18
|
shortcutIndex: record.shortcutIndex,
|
|
19
|
+
displayName,
|
|
18
20
|
spawnNearWorkerIds
|
|
19
21
|
};
|
|
20
22
|
}
|
|
@@ -24,11 +26,22 @@ function parseSpawnInput(body) {
|
|
|
24
26
|
projectId: record.projectId,
|
|
25
27
|
runtimeId: record.runtimeId,
|
|
26
28
|
command,
|
|
29
|
+
displayName,
|
|
27
30
|
spawnNearWorkerIds
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
throw (0, appError_1.validationError)("Invalid spawn request: expected shortcutIndex or projectId+runtimeId.", "spawn_invalid_payload");
|
|
31
34
|
}
|
|
35
|
+
function parseDisplayName(record) {
|
|
36
|
+
if (typeof record.displayName === "undefined") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (typeof record.displayName !== "string") {
|
|
40
|
+
throw (0, appError_1.validationError)("displayName must be a string when provided.", "spawn_invalid_display_name");
|
|
41
|
+
}
|
|
42
|
+
const trimmed = record.displayName.trim();
|
|
43
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
44
|
+
}
|
|
32
45
|
function parseSpawnNearWorkerIds(record) {
|
|
33
46
|
if (typeof record.spawnNearWorkerIds === "undefined") {
|
|
34
47
|
return undefined;
|
|
@@ -22,7 +22,7 @@ function resolveSpawnPlan(config, input) {
|
|
|
22
22
|
runtimeId: shortcut.runtime,
|
|
23
23
|
runtime,
|
|
24
24
|
command: shortcut.command ?? runtime.command,
|
|
25
|
-
displayName: shortcut.label,
|
|
25
|
+
displayName: input.displayName ?? shortcut.label,
|
|
26
26
|
avatar: shortcut.avatar
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -39,6 +39,7 @@ function resolveSpawnPlan(config, input) {
|
|
|
39
39
|
project,
|
|
40
40
|
runtimeId: input.runtimeId,
|
|
41
41
|
runtime,
|
|
42
|
-
command: input.command && input.command.length > 0 ? input.command : runtime.command
|
|
42
|
+
command: input.command && input.command.length > 0 ? input.command : runtime.command,
|
|
43
|
+
displayName: input.displayName
|
|
43
44
|
};
|
|
44
45
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.claudeSpawnGraceMs = exports.cachedActivityWindowMs = exports.stickyWorkingWindowMs = exports.commandWarmupWindowMs = exports.recentErrorSignalWindowMs = exports.parsedStrongEvidenceWindowMs = void 0;
|
|
3
|
+
exports.recoverableToolErrorMatchers = exports.fatalRuntimeErrorMatchers = exports.openCodeWorkingFreshWindowMs = exports.claudeWorkingFreshWindowMs = exports.genericWorkingFreshWindowMs = exports.openCodeSpawnGraceMs = exports.claudeSpawnGraceMs = exports.cachedActivityWindowMs = exports.stickyWorkingWindowMs = exports.commandWarmupWindowMs = exports.recentErrorSignalWindowMs = exports.parsedStrongEvidenceWindowMs = void 0;
|
|
4
4
|
const parsedStrongEvidenceWindowMs = 8_000;
|
|
5
5
|
exports.parsedStrongEvidenceWindowMs = parsedStrongEvidenceWindowMs;
|
|
6
6
|
const recentErrorSignalWindowMs = 15_000;
|
|
@@ -13,6 +13,8 @@ const cachedActivityWindowMs = 12_000;
|
|
|
13
13
|
exports.cachedActivityWindowMs = cachedActivityWindowMs;
|
|
14
14
|
const claudeSpawnGraceMs = 5_000;
|
|
15
15
|
exports.claudeSpawnGraceMs = claudeSpawnGraceMs;
|
|
16
|
+
const openCodeSpawnGraceMs = 5_000;
|
|
17
|
+
exports.openCodeSpawnGraceMs = openCodeSpawnGraceMs;
|
|
16
18
|
const genericWorkingFreshWindowMs = 12_000;
|
|
17
19
|
exports.genericWorkingFreshWindowMs = genericWorkingFreshWindowMs;
|
|
18
20
|
const claudeWorkingFreshWindowMs = 10_000;
|
|
@@ -137,4 +137,17 @@ function createContext(overrides = {}) {
|
|
|
137
137
|
(0, vitest_1.expect)(decision.activityTool).toBeUndefined();
|
|
138
138
|
(0, vitest_1.expect)(decision.reasons[0]?.code).toBe("opencode-prompt-idle");
|
|
139
139
|
});
|
|
140
|
+
(0, vitest_1.it)("returns idle for freshly spawned OpenCode sessions within grace window", () => {
|
|
141
|
+
const decision = (0, decision_1.deriveWorkerStatusDecision)(createContext({
|
|
142
|
+
isOpenCodeSession: true,
|
|
143
|
+
currentCommand: "opencode",
|
|
144
|
+
commandLower: "opencode",
|
|
145
|
+
hasOpenCodePromptSignal: false,
|
|
146
|
+
hasOpenCodeActiveSignal: false,
|
|
147
|
+
commandQuietForMs: 500,
|
|
148
|
+
workerAgeMs: 2_000
|
|
149
|
+
}));
|
|
150
|
+
(0, vitest_1.expect)(decision.status).toBe("idle");
|
|
151
|
+
(0, vitest_1.expect)(decision.reasons.some((r) => r.code === "opencode-spawn-grace-idle")).toBe(true);
|
|
152
|
+
});
|
|
140
153
|
});
|
|
@@ -41,6 +41,19 @@ function detectIdleBlocker(context, evidence) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
if (context.isOpenCodeSession &&
|
|
45
|
+
context.workerAgeMs <= constants_1.openCodeSpawnGraceMs &&
|
|
46
|
+
context.transcriptSnapshot?.status !== "working" &&
|
|
47
|
+
!context.hasOpenCodeActiveSignal &&
|
|
48
|
+
!evidence.parsedStrongSignal) {
|
|
49
|
+
return {
|
|
50
|
+
reason: {
|
|
51
|
+
code: "opencode-spawn-grace-idle",
|
|
52
|
+
message: "During early OpenCode spawn grace window without active signals.",
|
|
53
|
+
detail: `${Math.round(context.workerAgeMs)}ms since worker creation`
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
44
57
|
const activeWindowMs = (0, helpers_1.statusFreshnessWindowMs)(context);
|
|
45
58
|
if (context.outputQuietForMs > activeWindowMs && context.transcriptSnapshot?.status !== "working") {
|
|
46
59
|
return {
|
|
@@ -183,15 +183,14 @@ class TmuxAdapter {
|
|
|
183
183
|
}
|
|
184
184
|
const normalizedText = text.replace(/\r\n?/g, "\n");
|
|
185
185
|
if (normalizedText.length > 0) {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
186
|
+
const isMultiline = normalizedText.includes("\n");
|
|
187
|
+
if (isMultiline) {
|
|
188
|
+
await this.runTmux(["send-keys", "-t", target, "-l", "\x1b[200~"]);
|
|
189
|
+
await this.runTmux(["send-keys", "-t", target, "-l", normalizedText]);
|
|
190
|
+
await this.runTmux(["send-keys", "-t", target, "-l", "\x1b[201~"]);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
await this.runTmux(["send-keys", "-t", target, "-l", normalizedText]);
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
196
|
if (options?.submit ?? true) {
|