chatroom-cli 1.0.65 → 1.0.66
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/index.js +1124 -20
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10192,6 +10192,464 @@ var init_config2 = __esm(() => {
|
|
|
10192
10192
|
WEB_SERVER_PORT = parseInt(process.env.WEB_PORT || "3456", 10);
|
|
10193
10193
|
});
|
|
10194
10194
|
|
|
10195
|
+
// src/infrastructure/agent-drivers/process-driver.ts
|
|
10196
|
+
import { spawn } from "node:child_process";
|
|
10197
|
+
function buildCombinedPrompt(rolePrompt, initialMessage) {
|
|
10198
|
+
return `${rolePrompt}
|
|
10199
|
+
|
|
10200
|
+
${initialMessage}`;
|
|
10201
|
+
}
|
|
10202
|
+
|
|
10203
|
+
class ProcessDriver {
|
|
10204
|
+
async start(options) {
|
|
10205
|
+
const config3 = this.buildSpawnConfig(options);
|
|
10206
|
+
console.log(` Spawning ${this.harness} agent...`);
|
|
10207
|
+
console.log(` Working dir: ${options.workingDir}`);
|
|
10208
|
+
if (options.harnessVersion) {
|
|
10209
|
+
console.log(` Harness version: v${options.harnessVersion.version} (major: ${options.harnessVersion.major})`);
|
|
10210
|
+
}
|
|
10211
|
+
if (options.model) {
|
|
10212
|
+
console.log(` Model: ${options.model}`);
|
|
10213
|
+
}
|
|
10214
|
+
try {
|
|
10215
|
+
const childProcess = spawn(config3.command, config3.args, {
|
|
10216
|
+
cwd: options.workingDir,
|
|
10217
|
+
stdio: config3.stdio,
|
|
10218
|
+
detached: true,
|
|
10219
|
+
shell: false
|
|
10220
|
+
});
|
|
10221
|
+
if (config3.writePromptToStdin && config3.stdinPrompt) {
|
|
10222
|
+
childProcess.stdin?.write(config3.stdinPrompt);
|
|
10223
|
+
childProcess.stdin?.end();
|
|
10224
|
+
}
|
|
10225
|
+
config3.afterSpawn?.(childProcess);
|
|
10226
|
+
childProcess.unref();
|
|
10227
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
10228
|
+
if (childProcess.killed || childProcess.exitCode !== null) {
|
|
10229
|
+
return {
|
|
10230
|
+
success: false,
|
|
10231
|
+
message: `Agent process exited immediately (exit code: ${childProcess.exitCode})`
|
|
10232
|
+
};
|
|
10233
|
+
}
|
|
10234
|
+
const handle = {
|
|
10235
|
+
harness: this.harness,
|
|
10236
|
+
type: "process",
|
|
10237
|
+
pid: childProcess.pid,
|
|
10238
|
+
workingDir: options.workingDir
|
|
10239
|
+
};
|
|
10240
|
+
return {
|
|
10241
|
+
success: true,
|
|
10242
|
+
message: "Agent spawned successfully",
|
|
10243
|
+
handle
|
|
10244
|
+
};
|
|
10245
|
+
} catch (error) {
|
|
10246
|
+
return {
|
|
10247
|
+
success: false,
|
|
10248
|
+
message: `Failed to spawn agent: ${error.message}`
|
|
10249
|
+
};
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
async stop(handle) {
|
|
10253
|
+
if (handle.type !== "process" || !handle.pid) {
|
|
10254
|
+
throw new Error(`Cannot stop: handle has no PID (type=${handle.type})`);
|
|
10255
|
+
}
|
|
10256
|
+
process.kill(handle.pid, "SIGTERM");
|
|
10257
|
+
}
|
|
10258
|
+
async isAlive(handle) {
|
|
10259
|
+
if (handle.type !== "process" || !handle.pid) {
|
|
10260
|
+
return false;
|
|
10261
|
+
}
|
|
10262
|
+
try {
|
|
10263
|
+
process.kill(handle.pid, 0);
|
|
10264
|
+
return true;
|
|
10265
|
+
} catch {
|
|
10266
|
+
return false;
|
|
10267
|
+
}
|
|
10268
|
+
}
|
|
10269
|
+
async recover(_workingDir) {
|
|
10270
|
+
return [];
|
|
10271
|
+
}
|
|
10272
|
+
async listModels() {
|
|
10273
|
+
return [];
|
|
10274
|
+
}
|
|
10275
|
+
}
|
|
10276
|
+
var init_process_driver = () => {};
|
|
10277
|
+
|
|
10278
|
+
// src/infrastructure/machine/types.ts
|
|
10279
|
+
var AGENT_HARNESSES, AGENT_HARNESS_COMMANDS, MACHINE_CONFIG_VERSION = "1";
|
|
10280
|
+
var init_types = __esm(() => {
|
|
10281
|
+
AGENT_HARNESSES = ["opencode"];
|
|
10282
|
+
AGENT_HARNESS_COMMANDS = {
|
|
10283
|
+
opencode: "opencode"
|
|
10284
|
+
};
|
|
10285
|
+
});
|
|
10286
|
+
|
|
10287
|
+
// src/infrastructure/agent-drivers/opencode-process-driver.ts
|
|
10288
|
+
import { execSync } from "node:child_process";
|
|
10289
|
+
var OpenCodeProcessDriver;
|
|
10290
|
+
var init_opencode_process_driver = __esm(() => {
|
|
10291
|
+
init_process_driver();
|
|
10292
|
+
init_types();
|
|
10293
|
+
OpenCodeProcessDriver = class OpenCodeProcessDriver extends ProcessDriver {
|
|
10294
|
+
harness = "opencode";
|
|
10295
|
+
capabilities = {
|
|
10296
|
+
sessionPersistence: false,
|
|
10297
|
+
abort: false,
|
|
10298
|
+
modelSelection: true,
|
|
10299
|
+
compaction: false,
|
|
10300
|
+
eventStreaming: false,
|
|
10301
|
+
messageInjection: false,
|
|
10302
|
+
dynamicModelDiscovery: true
|
|
10303
|
+
};
|
|
10304
|
+
buildSpawnConfig(options) {
|
|
10305
|
+
const command = AGENT_HARNESS_COMMANDS[this.harness];
|
|
10306
|
+
const combinedPrompt = buildCombinedPrompt(options.rolePrompt, options.initialMessage);
|
|
10307
|
+
const args = ["run"];
|
|
10308
|
+
if (options.model) {
|
|
10309
|
+
args.push("--model", options.model);
|
|
10310
|
+
}
|
|
10311
|
+
return {
|
|
10312
|
+
command,
|
|
10313
|
+
args,
|
|
10314
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
10315
|
+
writePromptToStdin: true,
|
|
10316
|
+
stdinPrompt: combinedPrompt
|
|
10317
|
+
};
|
|
10318
|
+
}
|
|
10319
|
+
async listModels() {
|
|
10320
|
+
try {
|
|
10321
|
+
const output = execSync("opencode models", {
|
|
10322
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10323
|
+
timeout: 1e4
|
|
10324
|
+
}).toString().trim();
|
|
10325
|
+
if (!output)
|
|
10326
|
+
return [];
|
|
10327
|
+
return output.split(`
|
|
10328
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
10329
|
+
} catch {
|
|
10330
|
+
return [];
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
};
|
|
10334
|
+
});
|
|
10335
|
+
|
|
10336
|
+
// src/infrastructure/agent-drivers/registry.ts
|
|
10337
|
+
class DefaultDriverRegistry {
|
|
10338
|
+
drivers;
|
|
10339
|
+
constructor(drivers) {
|
|
10340
|
+
this.drivers = new Map;
|
|
10341
|
+
for (const driver of drivers) {
|
|
10342
|
+
this.drivers.set(driver.harness, driver);
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
get(harness) {
|
|
10346
|
+
const driver = this.drivers.get(harness);
|
|
10347
|
+
if (!driver) {
|
|
10348
|
+
throw new Error(`No driver registered for harness: ${harness}`);
|
|
10349
|
+
}
|
|
10350
|
+
return driver;
|
|
10351
|
+
}
|
|
10352
|
+
all() {
|
|
10353
|
+
return Array.from(this.drivers.values());
|
|
10354
|
+
}
|
|
10355
|
+
capabilities(harness) {
|
|
10356
|
+
return this.get(harness).capabilities;
|
|
10357
|
+
}
|
|
10358
|
+
}
|
|
10359
|
+
function getDriverRegistry() {
|
|
10360
|
+
if (!registryInstance) {
|
|
10361
|
+
registryInstance = new DefaultDriverRegistry([new OpenCodeProcessDriver]);
|
|
10362
|
+
}
|
|
10363
|
+
return registryInstance;
|
|
10364
|
+
}
|
|
10365
|
+
var registryInstance = null;
|
|
10366
|
+
var init_registry = __esm(() => {
|
|
10367
|
+
init_opencode_process_driver();
|
|
10368
|
+
});
|
|
10369
|
+
|
|
10370
|
+
// src/infrastructure/agent-drivers/index.ts
|
|
10371
|
+
var init_agent_drivers = __esm(() => {
|
|
10372
|
+
init_registry();
|
|
10373
|
+
init_process_driver();
|
|
10374
|
+
init_process_driver();
|
|
10375
|
+
init_opencode_process_driver();
|
|
10376
|
+
});
|
|
10377
|
+
|
|
10378
|
+
// src/infrastructure/machine/detection.ts
|
|
10379
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
10380
|
+
function commandExists(command) {
|
|
10381
|
+
try {
|
|
10382
|
+
const checkCommand = process.platform === "win32" ? `where ${command}` : `which ${command}`;
|
|
10383
|
+
execSync2(checkCommand, { stdio: "ignore" });
|
|
10384
|
+
return true;
|
|
10385
|
+
} catch {
|
|
10386
|
+
return false;
|
|
10387
|
+
}
|
|
10388
|
+
}
|
|
10389
|
+
function parseVersion(versionStr) {
|
|
10390
|
+
const match = versionStr.match(/v?(\d+)\.(\d+)\.(\d+)/);
|
|
10391
|
+
if (!match)
|
|
10392
|
+
return null;
|
|
10393
|
+
const major = parseInt(match[1], 10);
|
|
10394
|
+
const version2 = `${match[1]}.${match[2]}.${match[3]}`;
|
|
10395
|
+
return { version: version2, major };
|
|
10396
|
+
}
|
|
10397
|
+
function detectHarnessVersion(harness) {
|
|
10398
|
+
const versionCommand = HARNESS_VERSION_COMMANDS[harness];
|
|
10399
|
+
if (!versionCommand)
|
|
10400
|
+
return null;
|
|
10401
|
+
try {
|
|
10402
|
+
const output = execSync2(versionCommand, {
|
|
10403
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10404
|
+
timeout: 5000
|
|
10405
|
+
}).toString().trim();
|
|
10406
|
+
return parseVersion(output);
|
|
10407
|
+
} catch {
|
|
10408
|
+
return null;
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
function detectHarnessVersions(harnesses) {
|
|
10412
|
+
const versions = {};
|
|
10413
|
+
for (const harness of harnesses) {
|
|
10414
|
+
const version2 = detectHarnessVersion(harness);
|
|
10415
|
+
if (version2) {
|
|
10416
|
+
versions[harness] = version2;
|
|
10417
|
+
}
|
|
10418
|
+
}
|
|
10419
|
+
return versions;
|
|
10420
|
+
}
|
|
10421
|
+
function detectAvailableHarnesses() {
|
|
10422
|
+
const available = [];
|
|
10423
|
+
for (const harness of AGENT_HARNESSES) {
|
|
10424
|
+
const command = AGENT_HARNESS_COMMANDS[harness];
|
|
10425
|
+
if (commandExists(command)) {
|
|
10426
|
+
available.push(harness);
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
10429
|
+
return available;
|
|
10430
|
+
}
|
|
10431
|
+
var HARNESS_VERSION_COMMANDS;
|
|
10432
|
+
var init_detection = __esm(() => {
|
|
10433
|
+
init_types();
|
|
10434
|
+
HARNESS_VERSION_COMMANDS = {
|
|
10435
|
+
opencode: "opencode --version"
|
|
10436
|
+
};
|
|
10437
|
+
});
|
|
10438
|
+
|
|
10439
|
+
// src/infrastructure/machine/storage.ts
|
|
10440
|
+
import { randomUUID } from "node:crypto";
|
|
10441
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "node:fs";
|
|
10442
|
+
import { homedir as homedir2, hostname as hostname2 } from "node:os";
|
|
10443
|
+
import { join as join3 } from "node:path";
|
|
10444
|
+
function ensureConfigDir2() {
|
|
10445
|
+
if (!existsSync2(CHATROOM_DIR2)) {
|
|
10446
|
+
mkdirSync2(CHATROOM_DIR2, { recursive: true, mode: 448 });
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
function getMachineConfigPath() {
|
|
10450
|
+
return join3(CHATROOM_DIR2, MACHINE_FILE);
|
|
10451
|
+
}
|
|
10452
|
+
function loadConfigFile() {
|
|
10453
|
+
const configPath = getMachineConfigPath();
|
|
10454
|
+
if (!existsSync2(configPath)) {
|
|
10455
|
+
return null;
|
|
10456
|
+
}
|
|
10457
|
+
try {
|
|
10458
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
10459
|
+
return JSON.parse(content);
|
|
10460
|
+
} catch (error) {
|
|
10461
|
+
console.warn(`⚠️ Failed to read machine config at ${configPath}: ${error.message}`);
|
|
10462
|
+
console.warn(` The machine will re-register with a new identity on next startup.`);
|
|
10463
|
+
console.warn(` If this is unexpected, check the file for corruption.`);
|
|
10464
|
+
return null;
|
|
10465
|
+
}
|
|
10466
|
+
}
|
|
10467
|
+
function saveConfigFile(configFile) {
|
|
10468
|
+
ensureConfigDir2();
|
|
10469
|
+
const configPath = getMachineConfigPath();
|
|
10470
|
+
const tempPath = `${configPath}.tmp`;
|
|
10471
|
+
const content = JSON.stringify(configFile, null, 2);
|
|
10472
|
+
writeFileSync2(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
10473
|
+
renameSync(tempPath, configPath);
|
|
10474
|
+
}
|
|
10475
|
+
function loadMachineConfig() {
|
|
10476
|
+
const configFile = loadConfigFile();
|
|
10477
|
+
if (!configFile)
|
|
10478
|
+
return null;
|
|
10479
|
+
const convexUrl = getConvexUrl();
|
|
10480
|
+
return configFile.machines[convexUrl] ?? null;
|
|
10481
|
+
}
|
|
10482
|
+
function saveMachineConfig(config3) {
|
|
10483
|
+
const configFile = loadConfigFile() ?? {
|
|
10484
|
+
version: MACHINE_CONFIG_VERSION,
|
|
10485
|
+
machines: {}
|
|
10486
|
+
};
|
|
10487
|
+
const convexUrl = getConvexUrl();
|
|
10488
|
+
configFile.machines[convexUrl] = config3;
|
|
10489
|
+
saveConfigFile(configFile);
|
|
10490
|
+
}
|
|
10491
|
+
function createNewEndpointConfig() {
|
|
10492
|
+
const now = new Date().toISOString();
|
|
10493
|
+
const availableHarnesses = detectAvailableHarnesses();
|
|
10494
|
+
return {
|
|
10495
|
+
machineId: randomUUID(),
|
|
10496
|
+
hostname: hostname2(),
|
|
10497
|
+
os: process.platform,
|
|
10498
|
+
registeredAt: now,
|
|
10499
|
+
lastSyncedAt: now,
|
|
10500
|
+
availableHarnesses,
|
|
10501
|
+
harnessVersions: detectHarnessVersions(availableHarnesses),
|
|
10502
|
+
chatroomAgents: {}
|
|
10503
|
+
};
|
|
10504
|
+
}
|
|
10505
|
+
function ensureMachineRegistered() {
|
|
10506
|
+
let config3 = loadMachineConfig();
|
|
10507
|
+
if (!config3) {
|
|
10508
|
+
config3 = createNewEndpointConfig();
|
|
10509
|
+
saveMachineConfig(config3);
|
|
10510
|
+
} else {
|
|
10511
|
+
const now = new Date().toISOString();
|
|
10512
|
+
config3.availableHarnesses = detectAvailableHarnesses();
|
|
10513
|
+
config3.harnessVersions = detectHarnessVersions(config3.availableHarnesses);
|
|
10514
|
+
config3.lastSyncedAt = now;
|
|
10515
|
+
config3.hostname = hostname2();
|
|
10516
|
+
saveMachineConfig(config3);
|
|
10517
|
+
}
|
|
10518
|
+
return {
|
|
10519
|
+
machineId: config3.machineId,
|
|
10520
|
+
hostname: config3.hostname,
|
|
10521
|
+
os: config3.os,
|
|
10522
|
+
availableHarnesses: config3.availableHarnesses,
|
|
10523
|
+
harnessVersions: config3.harnessVersions
|
|
10524
|
+
};
|
|
10525
|
+
}
|
|
10526
|
+
function getMachineId() {
|
|
10527
|
+
const config3 = loadMachineConfig();
|
|
10528
|
+
return config3?.machineId ?? null;
|
|
10529
|
+
}
|
|
10530
|
+
function updateAgentContext(chatroomId, role, agentType, workingDir) {
|
|
10531
|
+
const config3 = loadMachineConfig();
|
|
10532
|
+
if (!config3) {
|
|
10533
|
+
throw new Error("Machine not registered. Run ensureMachineRegistered() first.");
|
|
10534
|
+
}
|
|
10535
|
+
const now = new Date().toISOString();
|
|
10536
|
+
if (!config3.chatroomAgents[chatroomId]) {
|
|
10537
|
+
config3.chatroomAgents[chatroomId] = {};
|
|
10538
|
+
}
|
|
10539
|
+
config3.chatroomAgents[chatroomId][role] = {
|
|
10540
|
+
agentType,
|
|
10541
|
+
workingDir,
|
|
10542
|
+
lastStartedAt: now
|
|
10543
|
+
};
|
|
10544
|
+
saveMachineConfig(config3);
|
|
10545
|
+
}
|
|
10546
|
+
function getAgentContext(chatroomId, role) {
|
|
10547
|
+
const config3 = loadMachineConfig();
|
|
10548
|
+
if (!config3) {
|
|
10549
|
+
return null;
|
|
10550
|
+
}
|
|
10551
|
+
return config3.chatroomAgents[chatroomId]?.[role] ?? null;
|
|
10552
|
+
}
|
|
10553
|
+
var CHATROOM_DIR2, MACHINE_FILE = "machine.json";
|
|
10554
|
+
var init_storage2 = __esm(() => {
|
|
10555
|
+
init_detection();
|
|
10556
|
+
init_types();
|
|
10557
|
+
init_client2();
|
|
10558
|
+
CHATROOM_DIR2 = join3(homedir2(), ".chatroom");
|
|
10559
|
+
});
|
|
10560
|
+
|
|
10561
|
+
// src/infrastructure/machine/daemon-state.ts
|
|
10562
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2 } from "node:fs";
|
|
10563
|
+
import { homedir as homedir3 } from "node:os";
|
|
10564
|
+
import { join as join4 } from "node:path";
|
|
10565
|
+
function agentKey(chatroomId, role) {
|
|
10566
|
+
return `${chatroomId}/${role}`;
|
|
10567
|
+
}
|
|
10568
|
+
function ensureStateDir() {
|
|
10569
|
+
if (!existsSync3(STATE_DIR)) {
|
|
10570
|
+
mkdirSync3(STATE_DIR, { recursive: true, mode: 448 });
|
|
10571
|
+
}
|
|
10572
|
+
}
|
|
10573
|
+
function stateFilePath(machineId) {
|
|
10574
|
+
return join4(STATE_DIR, `${machineId}.json`);
|
|
10575
|
+
}
|
|
10576
|
+
function loadDaemonState(machineId) {
|
|
10577
|
+
const filePath = stateFilePath(machineId);
|
|
10578
|
+
if (!existsSync3(filePath)) {
|
|
10579
|
+
return null;
|
|
10580
|
+
}
|
|
10581
|
+
try {
|
|
10582
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
10583
|
+
return JSON.parse(content);
|
|
10584
|
+
} catch {
|
|
10585
|
+
return null;
|
|
10586
|
+
}
|
|
10587
|
+
}
|
|
10588
|
+
function saveDaemonState(state) {
|
|
10589
|
+
ensureStateDir();
|
|
10590
|
+
const filePath = stateFilePath(state.machineId);
|
|
10591
|
+
const tempPath = `${filePath}.tmp`;
|
|
10592
|
+
const content = JSON.stringify(state, null, 2);
|
|
10593
|
+
writeFileSync3(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
10594
|
+
renameSync2(tempPath, filePath);
|
|
10595
|
+
}
|
|
10596
|
+
function loadOrCreate(machineId) {
|
|
10597
|
+
return loadDaemonState(machineId) ?? {
|
|
10598
|
+
version: STATE_VERSION,
|
|
10599
|
+
machineId,
|
|
10600
|
+
updatedAt: new Date().toISOString(),
|
|
10601
|
+
agents: {}
|
|
10602
|
+
};
|
|
10603
|
+
}
|
|
10604
|
+
function persistAgentPid(machineId, chatroomId, role, pid, harness) {
|
|
10605
|
+
const state = loadOrCreate(machineId);
|
|
10606
|
+
state.agents[agentKey(chatroomId, role)] = {
|
|
10607
|
+
pid,
|
|
10608
|
+
harness,
|
|
10609
|
+
startedAt: new Date().toISOString()
|
|
10610
|
+
};
|
|
10611
|
+
state.updatedAt = new Date().toISOString();
|
|
10612
|
+
saveDaemonState(state);
|
|
10613
|
+
}
|
|
10614
|
+
function clearAgentPid(machineId, chatroomId, role) {
|
|
10615
|
+
const state = loadDaemonState(machineId);
|
|
10616
|
+
if (!state)
|
|
10617
|
+
return;
|
|
10618
|
+
const key = agentKey(chatroomId, role);
|
|
10619
|
+
if (!(key in state.agents))
|
|
10620
|
+
return;
|
|
10621
|
+
delete state.agents[key];
|
|
10622
|
+
state.updatedAt = new Date().toISOString();
|
|
10623
|
+
saveDaemonState(state);
|
|
10624
|
+
}
|
|
10625
|
+
function listAgentEntries(machineId) {
|
|
10626
|
+
const state = loadDaemonState(machineId);
|
|
10627
|
+
if (!state)
|
|
10628
|
+
return [];
|
|
10629
|
+
const results = [];
|
|
10630
|
+
for (const [key, entry] of Object.entries(state.agents)) {
|
|
10631
|
+
const separatorIndex = key.lastIndexOf("/");
|
|
10632
|
+
if (separatorIndex === -1)
|
|
10633
|
+
continue;
|
|
10634
|
+
const chatroomId = key.substring(0, separatorIndex);
|
|
10635
|
+
const role = key.substring(separatorIndex + 1);
|
|
10636
|
+
results.push({ chatroomId, role, entry });
|
|
10637
|
+
}
|
|
10638
|
+
return results;
|
|
10639
|
+
}
|
|
10640
|
+
var CHATROOM_DIR3, STATE_DIR, STATE_VERSION = "1";
|
|
10641
|
+
var init_daemon_state = __esm(() => {
|
|
10642
|
+
CHATROOM_DIR3 = join4(homedir3(), ".chatroom");
|
|
10643
|
+
STATE_DIR = join4(CHATROOM_DIR3, "machines", "state");
|
|
10644
|
+
});
|
|
10645
|
+
|
|
10646
|
+
// src/infrastructure/machine/index.ts
|
|
10647
|
+
var init_machine = __esm(() => {
|
|
10648
|
+
init_types();
|
|
10649
|
+
init_storage2();
|
|
10650
|
+
init_daemon_state();
|
|
10651
|
+
});
|
|
10652
|
+
|
|
10195
10653
|
// src/commands/wait-for-task.ts
|
|
10196
10654
|
var exports_wait_for_task = {};
|
|
10197
10655
|
__export(exports_wait_for_task, {
|
|
@@ -10264,6 +10722,45 @@ async function waitForTask(chatroomId, options) {
|
|
|
10264
10722
|
console.error(`❌ Chatroom ${chatroomId} not found or access denied`);
|
|
10265
10723
|
process.exit(1);
|
|
10266
10724
|
}
|
|
10725
|
+
try {
|
|
10726
|
+
const machineInfo = ensureMachineRegistered();
|
|
10727
|
+
let availableModels = [];
|
|
10728
|
+
try {
|
|
10729
|
+
const registry = getDriverRegistry();
|
|
10730
|
+
for (const driver of registry.all()) {
|
|
10731
|
+
if (driver.capabilities.dynamicModelDiscovery) {
|
|
10732
|
+
const models = await driver.listModels();
|
|
10733
|
+
availableModels = availableModels.concat(models);
|
|
10734
|
+
}
|
|
10735
|
+
}
|
|
10736
|
+
} catch {}
|
|
10737
|
+
await client2.mutation(api.machines.register, {
|
|
10738
|
+
sessionId,
|
|
10739
|
+
machineId: machineInfo.machineId,
|
|
10740
|
+
hostname: machineInfo.hostname,
|
|
10741
|
+
os: machineInfo.os,
|
|
10742
|
+
availableHarnesses: machineInfo.availableHarnesses,
|
|
10743
|
+
harnessVersions: machineInfo.harnessVersions,
|
|
10744
|
+
availableModels
|
|
10745
|
+
});
|
|
10746
|
+
const agentType = options.agentType ?? (machineInfo.availableHarnesses.length > 0 ? machineInfo.availableHarnesses[0] : undefined);
|
|
10747
|
+
if (agentType) {
|
|
10748
|
+
const workingDir = process.cwd();
|
|
10749
|
+
updateAgentContext(chatroomId, role, agentType, workingDir);
|
|
10750
|
+
await client2.mutation(api.machines.updateAgentConfig, {
|
|
10751
|
+
sessionId,
|
|
10752
|
+
machineId: machineInfo.machineId,
|
|
10753
|
+
chatroomId,
|
|
10754
|
+
role,
|
|
10755
|
+
agentType,
|
|
10756
|
+
workingDir
|
|
10757
|
+
});
|
|
10758
|
+
}
|
|
10759
|
+
} catch (machineError) {
|
|
10760
|
+
if (!silent) {
|
|
10761
|
+
console.warn(`⚠️ Machine registration failed: ${machineError.message}`);
|
|
10762
|
+
}
|
|
10763
|
+
}
|
|
10267
10764
|
const effectiveTimeout = timeout || DEFAULT_WAIT_TIMEOUT_MS;
|
|
10268
10765
|
const readyUntil = Date.now() + effectiveTimeout;
|
|
10269
10766
|
const connectionId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -10385,20 +10882,6 @@ ${"─".repeat(50)}`);
|
|
|
10385
10882
|
status: "active",
|
|
10386
10883
|
expiresAt: activeUntil
|
|
10387
10884
|
});
|
|
10388
|
-
if (message && message.type === "interrupt") {
|
|
10389
|
-
const interruptTime = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
10390
|
-
console.log(`
|
|
10391
|
-
${"─".repeat(50)}`);
|
|
10392
|
-
console.log(`⚠️ RECONNECTION REQUIRED
|
|
10393
|
-
`);
|
|
10394
|
-
console.log(`[${interruptTime}] Why: Interrupt message received from team`);
|
|
10395
|
-
console.log(`Impact: You are no longer listening for tasks`);
|
|
10396
|
-
console.log(`Action: Run this command immediately to resume availability
|
|
10397
|
-
`);
|
|
10398
|
-
console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
|
|
10399
|
-
console.log(`${"─".repeat(50)}`);
|
|
10400
|
-
process.exit(0);
|
|
10401
|
-
}
|
|
10402
10885
|
const taskDeliveryPrompt = await client2.query(api.messages.getTaskDeliveryPrompt, {
|
|
10403
10886
|
sessionId,
|
|
10404
10887
|
chatroomId,
|
|
@@ -10551,8 +11034,10 @@ var init_wait_for_task = __esm(() => {
|
|
|
10551
11034
|
init_env();
|
|
10552
11035
|
init_api3();
|
|
10553
11036
|
init_config2();
|
|
11037
|
+
init_agent_drivers();
|
|
10554
11038
|
init_storage();
|
|
10555
11039
|
init_client2();
|
|
11040
|
+
init_machine();
|
|
10556
11041
|
});
|
|
10557
11042
|
|
|
10558
11043
|
// src/commands/task-started.ts
|
|
@@ -11573,12 +12058,12 @@ __export(exports_file_content, {
|
|
|
11573
12058
|
resolveContent: () => resolveContent,
|
|
11574
12059
|
readFileContent: () => readFileContent
|
|
11575
12060
|
});
|
|
11576
|
-
import { readFileSync as
|
|
12061
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
11577
12062
|
import { resolve } from "path";
|
|
11578
12063
|
function readFileContent(filePath, optionName) {
|
|
11579
12064
|
const absolutePath = resolve(process.cwd(), filePath);
|
|
11580
12065
|
try {
|
|
11581
|
-
return
|
|
12066
|
+
return readFileSync5(absolutePath, "utf-8");
|
|
11582
12067
|
} catch (err) {
|
|
11583
12068
|
const nodeErr = err;
|
|
11584
12069
|
throw new Error(`Cannot read file for --${optionName}: ${absolutePath}
|
|
@@ -12062,6 +12547,610 @@ var init_artifact = __esm(() => {
|
|
|
12062
12547
|
init_file_content();
|
|
12063
12548
|
});
|
|
12064
12549
|
|
|
12550
|
+
// src/commands/machine/pid.ts
|
|
12551
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
12552
|
+
import { homedir as homedir4 } from "node:os";
|
|
12553
|
+
import { join as join5 } from "node:path";
|
|
12554
|
+
function ensureChatroomDir() {
|
|
12555
|
+
if (!existsSync4(CHATROOM_DIR4)) {
|
|
12556
|
+
mkdirSync4(CHATROOM_DIR4, { recursive: true, mode: 448 });
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
12559
|
+
function getPidFilePath() {
|
|
12560
|
+
return join5(CHATROOM_DIR4, PID_FILE);
|
|
12561
|
+
}
|
|
12562
|
+
function isProcessRunning(pid) {
|
|
12563
|
+
try {
|
|
12564
|
+
process.kill(pid, 0);
|
|
12565
|
+
return true;
|
|
12566
|
+
} catch {
|
|
12567
|
+
return false;
|
|
12568
|
+
}
|
|
12569
|
+
}
|
|
12570
|
+
function readPid() {
|
|
12571
|
+
const pidPath = getPidFilePath();
|
|
12572
|
+
if (!existsSync4(pidPath)) {
|
|
12573
|
+
return null;
|
|
12574
|
+
}
|
|
12575
|
+
try {
|
|
12576
|
+
const content = readFileSync6(pidPath, "utf-8").trim();
|
|
12577
|
+
const pid = parseInt(content, 10);
|
|
12578
|
+
if (isNaN(pid) || pid <= 0) {
|
|
12579
|
+
return null;
|
|
12580
|
+
}
|
|
12581
|
+
return pid;
|
|
12582
|
+
} catch {
|
|
12583
|
+
return null;
|
|
12584
|
+
}
|
|
12585
|
+
}
|
|
12586
|
+
function writePid() {
|
|
12587
|
+
ensureChatroomDir();
|
|
12588
|
+
const pidPath = getPidFilePath();
|
|
12589
|
+
writeFileSync4(pidPath, process.pid.toString(), "utf-8");
|
|
12590
|
+
}
|
|
12591
|
+
function removePid() {
|
|
12592
|
+
const pidPath = getPidFilePath();
|
|
12593
|
+
try {
|
|
12594
|
+
if (existsSync4(pidPath)) {
|
|
12595
|
+
unlinkSync2(pidPath);
|
|
12596
|
+
}
|
|
12597
|
+
} catch {}
|
|
12598
|
+
}
|
|
12599
|
+
function isDaemonRunning() {
|
|
12600
|
+
const pid = readPid();
|
|
12601
|
+
if (pid === null) {
|
|
12602
|
+
return { running: false, pid: null };
|
|
12603
|
+
}
|
|
12604
|
+
if (isProcessRunning(pid)) {
|
|
12605
|
+
return { running: true, pid };
|
|
12606
|
+
}
|
|
12607
|
+
removePid();
|
|
12608
|
+
return { running: false, pid: null };
|
|
12609
|
+
}
|
|
12610
|
+
function acquireLock() {
|
|
12611
|
+
const { running, pid } = isDaemonRunning();
|
|
12612
|
+
if (running) {
|
|
12613
|
+
console.error(`❌ Daemon already running (PID: ${pid})`);
|
|
12614
|
+
return false;
|
|
12615
|
+
}
|
|
12616
|
+
writePid();
|
|
12617
|
+
return true;
|
|
12618
|
+
}
|
|
12619
|
+
function releaseLock() {
|
|
12620
|
+
removePid();
|
|
12621
|
+
}
|
|
12622
|
+
var CHATROOM_DIR4, PID_FILE = "daemon.pid";
|
|
12623
|
+
var init_pid = __esm(() => {
|
|
12624
|
+
CHATROOM_DIR4 = join5(homedir4(), ".chatroom");
|
|
12625
|
+
});
|
|
12626
|
+
|
|
12627
|
+
// src/commands/machine/daemon-start.ts
|
|
12628
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
12629
|
+
import { stat } from "node:fs/promises";
|
|
12630
|
+
function parseMachineCommand(raw) {
|
|
12631
|
+
switch (raw.type) {
|
|
12632
|
+
case "ping":
|
|
12633
|
+
return { _id: raw._id, type: "ping", payload: {}, createdAt: raw.createdAt };
|
|
12634
|
+
case "status":
|
|
12635
|
+
return { _id: raw._id, type: "status", payload: {}, createdAt: raw.createdAt };
|
|
12636
|
+
case "start-agent": {
|
|
12637
|
+
const { chatroomId, role, agentHarness } = raw.payload;
|
|
12638
|
+
if (!chatroomId || !role || !agentHarness) {
|
|
12639
|
+
console.error(` ⚠️ Invalid start-agent command: missing chatroomId, role, or agentHarness`);
|
|
12640
|
+
return null;
|
|
12641
|
+
}
|
|
12642
|
+
return {
|
|
12643
|
+
_id: raw._id,
|
|
12644
|
+
type: "start-agent",
|
|
12645
|
+
payload: {
|
|
12646
|
+
chatroomId,
|
|
12647
|
+
role,
|
|
12648
|
+
agentHarness,
|
|
12649
|
+
model: raw.payload.model,
|
|
12650
|
+
workingDir: raw.payload.workingDir
|
|
12651
|
+
},
|
|
12652
|
+
createdAt: raw.createdAt
|
|
12653
|
+
};
|
|
12654
|
+
}
|
|
12655
|
+
case "stop-agent": {
|
|
12656
|
+
const { chatroomId, role } = raw.payload;
|
|
12657
|
+
if (!chatroomId || !role) {
|
|
12658
|
+
console.error(` ⚠️ Invalid stop-agent command: missing chatroomId or role`);
|
|
12659
|
+
return null;
|
|
12660
|
+
}
|
|
12661
|
+
return {
|
|
12662
|
+
_id: raw._id,
|
|
12663
|
+
type: "stop-agent",
|
|
12664
|
+
payload: { chatroomId, role },
|
|
12665
|
+
createdAt: raw.createdAt
|
|
12666
|
+
};
|
|
12667
|
+
}
|
|
12668
|
+
default:
|
|
12669
|
+
return null;
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
function formatTimestamp() {
|
|
12673
|
+
return new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
12674
|
+
}
|
|
12675
|
+
function verifyPidOwnership(pid, expectedHarness) {
|
|
12676
|
+
try {
|
|
12677
|
+
process.kill(pid, 0);
|
|
12678
|
+
} catch {
|
|
12679
|
+
return false;
|
|
12680
|
+
}
|
|
12681
|
+
if (!expectedHarness) {
|
|
12682
|
+
return true;
|
|
12683
|
+
}
|
|
12684
|
+
try {
|
|
12685
|
+
const platform = process.platform;
|
|
12686
|
+
let processName = "";
|
|
12687
|
+
if (platform === "darwin" || platform === "linux") {
|
|
12688
|
+
processName = execSync3(`ps -p ${pid} -o comm= 2>/dev/null`, {
|
|
12689
|
+
encoding: "utf-8",
|
|
12690
|
+
timeout: 3000
|
|
12691
|
+
}).trim();
|
|
12692
|
+
}
|
|
12693
|
+
if (!processName) {
|
|
12694
|
+
return true;
|
|
12695
|
+
}
|
|
12696
|
+
const harnessLower = expectedHarness.toLowerCase();
|
|
12697
|
+
const procLower = processName.toLowerCase();
|
|
12698
|
+
return procLower.includes(harnessLower) || procLower.includes("node") || procLower.includes("bun");
|
|
12699
|
+
} catch {
|
|
12700
|
+
return true;
|
|
12701
|
+
}
|
|
12702
|
+
}
|
|
12703
|
+
async function clearAgentPidEverywhere(ctx, chatroomId, role) {
|
|
12704
|
+
try {
|
|
12705
|
+
await ctx.client.mutation(api.machines.updateSpawnedAgent, {
|
|
12706
|
+
sessionId: ctx.sessionId,
|
|
12707
|
+
machineId: ctx.machineId,
|
|
12708
|
+
chatroomId,
|
|
12709
|
+
role,
|
|
12710
|
+
pid: undefined
|
|
12711
|
+
});
|
|
12712
|
+
} catch (e) {
|
|
12713
|
+
console.log(` ⚠️ Failed to clear PID in backend: ${e.message}`);
|
|
12714
|
+
}
|
|
12715
|
+
clearAgentPid(ctx.machineId, chatroomId, role);
|
|
12716
|
+
}
|
|
12717
|
+
async function recoverAgentState(ctx) {
|
|
12718
|
+
const entries = listAgentEntries(ctx.machineId);
|
|
12719
|
+
if (entries.length === 0) {
|
|
12720
|
+
console.log(` No agent entries found — nothing to recover`);
|
|
12721
|
+
return;
|
|
12722
|
+
}
|
|
12723
|
+
let recovered = 0;
|
|
12724
|
+
let cleared = 0;
|
|
12725
|
+
for (const { chatroomId, role, entry } of entries) {
|
|
12726
|
+
const { pid, harness } = entry;
|
|
12727
|
+
const alive = verifyPidOwnership(pid, harness);
|
|
12728
|
+
if (alive) {
|
|
12729
|
+
console.log(` ✅ Recovered: ${role} (PID ${pid}, harness: ${harness})`);
|
|
12730
|
+
recovered++;
|
|
12731
|
+
} else {
|
|
12732
|
+
console.log(` \uD83E\uDDF9 Stale PID ${pid} for ${role} — clearing`);
|
|
12733
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
12734
|
+
cleared++;
|
|
12735
|
+
}
|
|
12736
|
+
}
|
|
12737
|
+
console.log(` Recovery complete: ${recovered} alive, ${cleared} stale cleared`);
|
|
12738
|
+
}
|
|
12739
|
+
function handlePing() {
|
|
12740
|
+
console.log(` ↪ Responding: pong`);
|
|
12741
|
+
return { result: "pong", failed: false };
|
|
12742
|
+
}
|
|
12743
|
+
function handleStatus(ctx) {
|
|
12744
|
+
const result = JSON.stringify({
|
|
12745
|
+
hostname: ctx.config?.hostname,
|
|
12746
|
+
os: ctx.config?.os,
|
|
12747
|
+
availableHarnesses: ctx.config?.availableHarnesses,
|
|
12748
|
+
chatroomAgents: Object.keys(ctx.config?.chatroomAgents ?? {})
|
|
12749
|
+
});
|
|
12750
|
+
console.log(` ↪ Responding with status`);
|
|
12751
|
+
return { result, failed: false };
|
|
12752
|
+
}
|
|
12753
|
+
async function handleStartAgent(ctx, command) {
|
|
12754
|
+
const { chatroomId, role, agentHarness, model, workingDir } = command.payload;
|
|
12755
|
+
console.log(` ↪ start-agent command received`);
|
|
12756
|
+
console.log(` Chatroom: ${chatroomId}`);
|
|
12757
|
+
console.log(` Role: ${role}`);
|
|
12758
|
+
console.log(` Harness: ${agentHarness}`);
|
|
12759
|
+
if (model) {
|
|
12760
|
+
console.log(` Model: ${model}`);
|
|
12761
|
+
}
|
|
12762
|
+
let agentContext = getAgentContext(chatroomId, role);
|
|
12763
|
+
if (!agentContext && workingDir) {
|
|
12764
|
+
console.log(` No local agent context, using workingDir from command payload`);
|
|
12765
|
+
updateAgentContext(chatroomId, role, agentHarness, workingDir);
|
|
12766
|
+
agentContext = getAgentContext(chatroomId, role);
|
|
12767
|
+
}
|
|
12768
|
+
if (!agentContext) {
|
|
12769
|
+
const msg = `No agent context found for ${chatroomId}/${role}`;
|
|
12770
|
+
console.log(` ⚠️ ${msg}`);
|
|
12771
|
+
return { result: msg, failed: true };
|
|
12772
|
+
}
|
|
12773
|
+
console.log(` Working dir: ${agentContext.workingDir}`);
|
|
12774
|
+
try {
|
|
12775
|
+
const dirStat = await stat(agentContext.workingDir);
|
|
12776
|
+
if (!dirStat.isDirectory()) {
|
|
12777
|
+
const msg = `Working directory is not a directory: ${agentContext.workingDir}`;
|
|
12778
|
+
console.log(` ⚠️ ${msg}`);
|
|
12779
|
+
return { result: msg, failed: true };
|
|
12780
|
+
}
|
|
12781
|
+
} catch {
|
|
12782
|
+
const msg = `Working directory does not exist: ${agentContext.workingDir}`;
|
|
12783
|
+
console.log(` ⚠️ ${msg}`);
|
|
12784
|
+
return { result: msg, failed: true };
|
|
12785
|
+
}
|
|
12786
|
+
const convexUrl = getConvexUrl();
|
|
12787
|
+
const initPromptResult = await ctx.client.query(api.messages.getInitPrompt, {
|
|
12788
|
+
sessionId: ctx.sessionId,
|
|
12789
|
+
chatroomId,
|
|
12790
|
+
role,
|
|
12791
|
+
convexUrl
|
|
12792
|
+
});
|
|
12793
|
+
if (!initPromptResult?.prompt) {
|
|
12794
|
+
const msg = "Failed to fetch init prompt from backend";
|
|
12795
|
+
console.log(` ⚠️ ${msg}`);
|
|
12796
|
+
return { result: msg, failed: true };
|
|
12797
|
+
}
|
|
12798
|
+
console.log(` Fetched split init prompt from backend`);
|
|
12799
|
+
const harnessVersion = ctx.config?.harnessVersions?.[agentHarness];
|
|
12800
|
+
const registry = getDriverRegistry();
|
|
12801
|
+
let driver;
|
|
12802
|
+
try {
|
|
12803
|
+
driver = registry.get(agentHarness);
|
|
12804
|
+
} catch {
|
|
12805
|
+
const msg = `No driver registered for harness: ${agentHarness}`;
|
|
12806
|
+
console.log(` ⚠️ ${msg}`);
|
|
12807
|
+
return { result: msg, failed: true };
|
|
12808
|
+
}
|
|
12809
|
+
const startResult = await driver.start({
|
|
12810
|
+
workingDir: agentContext.workingDir,
|
|
12811
|
+
rolePrompt: initPromptResult.rolePrompt,
|
|
12812
|
+
initialMessage: initPromptResult.initialMessage,
|
|
12813
|
+
harnessVersion: harnessVersion ?? undefined,
|
|
12814
|
+
model
|
|
12815
|
+
});
|
|
12816
|
+
if (startResult.success && startResult.handle) {
|
|
12817
|
+
const msg = `Agent spawned (PID: ${startResult.handle.pid})`;
|
|
12818
|
+
console.log(` ✅ ${msg}`);
|
|
12819
|
+
if (startResult.handle.pid) {
|
|
12820
|
+
try {
|
|
12821
|
+
await ctx.client.mutation(api.machines.updateSpawnedAgent, {
|
|
12822
|
+
sessionId: ctx.sessionId,
|
|
12823
|
+
machineId: ctx.machineId,
|
|
12824
|
+
chatroomId,
|
|
12825
|
+
role,
|
|
12826
|
+
pid: startResult.handle.pid
|
|
12827
|
+
});
|
|
12828
|
+
console.log(` Updated backend with PID: ${startResult.handle.pid}`);
|
|
12829
|
+
persistAgentPid(ctx.machineId, chatroomId, role, startResult.handle.pid, agentHarness);
|
|
12830
|
+
} catch (e) {
|
|
12831
|
+
console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
return { result: msg, failed: false };
|
|
12835
|
+
}
|
|
12836
|
+
console.log(` ⚠️ ${startResult.message}`);
|
|
12837
|
+
return { result: startResult.message, failed: true };
|
|
12838
|
+
}
|
|
12839
|
+
async function handleStopAgent(ctx, command) {
|
|
12840
|
+
const { chatroomId, role } = command.payload;
|
|
12841
|
+
console.log(` ↪ stop-agent command received`);
|
|
12842
|
+
console.log(` Chatroom: ${chatroomId}`);
|
|
12843
|
+
console.log(` Role: ${role}`);
|
|
12844
|
+
const configsResult = await ctx.client.query(api.machines.getAgentConfigs, {
|
|
12845
|
+
sessionId: ctx.sessionId,
|
|
12846
|
+
chatroomId
|
|
12847
|
+
});
|
|
12848
|
+
const targetConfig = configsResult.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
|
|
12849
|
+
if (!targetConfig?.spawnedAgentPid) {
|
|
12850
|
+
const msg = "No running agent found (no PID recorded)";
|
|
12851
|
+
console.log(` ⚠️ ${msg}`);
|
|
12852
|
+
return { result: msg, failed: true };
|
|
12853
|
+
}
|
|
12854
|
+
const pidToKill = targetConfig.spawnedAgentPid;
|
|
12855
|
+
const agentHarness = targetConfig.agentType || undefined;
|
|
12856
|
+
console.log(` Stopping agent with PID: ${pidToKill}`);
|
|
12857
|
+
const stopHandle = {
|
|
12858
|
+
harness: agentHarness || "opencode",
|
|
12859
|
+
type: "process",
|
|
12860
|
+
pid: pidToKill,
|
|
12861
|
+
workingDir: ""
|
|
12862
|
+
};
|
|
12863
|
+
const registry = getDriverRegistry();
|
|
12864
|
+
let stopDriver;
|
|
12865
|
+
try {
|
|
12866
|
+
stopDriver = agentHarness ? registry.get(agentHarness) : null;
|
|
12867
|
+
} catch {
|
|
12868
|
+
stopDriver = null;
|
|
12869
|
+
}
|
|
12870
|
+
const isAlive = stopDriver ? await stopDriver.isAlive(stopHandle) : verifyPidOwnership(pidToKill, agentHarness);
|
|
12871
|
+
if (!isAlive) {
|
|
12872
|
+
console.log(` ⚠️ PID ${pidToKill} does not appear to belong to the expected agent`);
|
|
12873
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
12874
|
+
console.log(` Cleared stale PID`);
|
|
12875
|
+
return {
|
|
12876
|
+
result: `PID ${pidToKill} appears stale (process not found or belongs to different program)`,
|
|
12877
|
+
failed: true
|
|
12878
|
+
};
|
|
12879
|
+
}
|
|
12880
|
+
try {
|
|
12881
|
+
if (stopDriver) {
|
|
12882
|
+
await stopDriver.stop(stopHandle);
|
|
12883
|
+
} else {
|
|
12884
|
+
process.kill(pidToKill, "SIGTERM");
|
|
12885
|
+
}
|
|
12886
|
+
const msg = `Agent stopped (PID: ${pidToKill})`;
|
|
12887
|
+
console.log(` ✅ ${msg}`);
|
|
12888
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
12889
|
+
console.log(` Cleared PID`);
|
|
12890
|
+
return { result: msg, failed: false };
|
|
12891
|
+
} catch (e) {
|
|
12892
|
+
const err = e;
|
|
12893
|
+
if (err.code === "ESRCH") {
|
|
12894
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
12895
|
+
const msg2 = "Process not found (may have already exited)";
|
|
12896
|
+
console.log(` ⚠️ ${msg2}`);
|
|
12897
|
+
return { result: msg2, failed: true };
|
|
12898
|
+
}
|
|
12899
|
+
const msg = `Failed to stop agent: ${err.message}`;
|
|
12900
|
+
console.log(` ⚠️ ${msg}`);
|
|
12901
|
+
return { result: msg, failed: true };
|
|
12902
|
+
}
|
|
12903
|
+
}
|
|
12904
|
+
async function processCommand(ctx, command) {
|
|
12905
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDCE8 Command received: ${command.type}`);
|
|
12906
|
+
try {
|
|
12907
|
+
await ctx.client.mutation(api.machines.ackCommand, {
|
|
12908
|
+
sessionId: ctx.sessionId,
|
|
12909
|
+
commandId: command._id,
|
|
12910
|
+
status: "processing"
|
|
12911
|
+
});
|
|
12912
|
+
let commandResult;
|
|
12913
|
+
switch (command.type) {
|
|
12914
|
+
case "ping":
|
|
12915
|
+
commandResult = handlePing();
|
|
12916
|
+
break;
|
|
12917
|
+
case "status":
|
|
12918
|
+
commandResult = handleStatus(ctx);
|
|
12919
|
+
break;
|
|
12920
|
+
case "start-agent":
|
|
12921
|
+
commandResult = await handleStartAgent(ctx, command);
|
|
12922
|
+
break;
|
|
12923
|
+
case "stop-agent":
|
|
12924
|
+
commandResult = await handleStopAgent(ctx, command);
|
|
12925
|
+
break;
|
|
12926
|
+
default: {
|
|
12927
|
+
const _exhaustive = command;
|
|
12928
|
+
commandResult = {
|
|
12929
|
+
result: `Unknown command type: ${_exhaustive.type}`,
|
|
12930
|
+
failed: true
|
|
12931
|
+
};
|
|
12932
|
+
}
|
|
12933
|
+
}
|
|
12934
|
+
const finalStatus = commandResult.failed ? "failed" : "completed";
|
|
12935
|
+
await ctx.client.mutation(api.machines.ackCommand, {
|
|
12936
|
+
sessionId: ctx.sessionId,
|
|
12937
|
+
commandId: command._id,
|
|
12938
|
+
status: finalStatus,
|
|
12939
|
+
result: commandResult.result
|
|
12940
|
+
});
|
|
12941
|
+
if (commandResult.failed) {
|
|
12942
|
+
console.log(` ❌ Command failed: ${commandResult.result}`);
|
|
12943
|
+
} else {
|
|
12944
|
+
console.log(` ✅ Command completed`);
|
|
12945
|
+
}
|
|
12946
|
+
} catch (error) {
|
|
12947
|
+
console.error(` ❌ Command failed: ${error.message}`);
|
|
12948
|
+
try {
|
|
12949
|
+
await ctx.client.mutation(api.machines.ackCommand, {
|
|
12950
|
+
sessionId: ctx.sessionId,
|
|
12951
|
+
commandId: command._id,
|
|
12952
|
+
status: "failed",
|
|
12953
|
+
result: error.message
|
|
12954
|
+
});
|
|
12955
|
+
} catch {}
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12958
|
+
async function initDaemon() {
|
|
12959
|
+
if (!acquireLock()) {
|
|
12960
|
+
process.exit(1);
|
|
12961
|
+
}
|
|
12962
|
+
const convexUrl = getConvexUrl();
|
|
12963
|
+
const sessionId = getSessionId();
|
|
12964
|
+
if (!sessionId) {
|
|
12965
|
+
const otherUrls = getOtherSessionUrls();
|
|
12966
|
+
console.error(`❌ Not authenticated for: ${convexUrl}`);
|
|
12967
|
+
if (otherUrls.length > 0) {
|
|
12968
|
+
console.error(`
|
|
12969
|
+
\uD83D\uDCA1 You have sessions for other environments:`);
|
|
12970
|
+
for (const url of otherUrls) {
|
|
12971
|
+
console.error(` • ${url}`);
|
|
12972
|
+
}
|
|
12973
|
+
}
|
|
12974
|
+
console.error(`
|
|
12975
|
+
Run: chatroom auth login`);
|
|
12976
|
+
releaseLock();
|
|
12977
|
+
process.exit(1);
|
|
12978
|
+
}
|
|
12979
|
+
const machineId = getMachineId();
|
|
12980
|
+
if (!machineId) {
|
|
12981
|
+
console.error(`❌ Machine not registered`);
|
|
12982
|
+
console.error(`
|
|
12983
|
+
Run any chatroom command first to register this machine,`);
|
|
12984
|
+
console.error(`for example: chatroom auth status`);
|
|
12985
|
+
releaseLock();
|
|
12986
|
+
process.exit(1);
|
|
12987
|
+
}
|
|
12988
|
+
const client2 = await getConvexClient();
|
|
12989
|
+
const typedSessionId = sessionId;
|
|
12990
|
+
try {
|
|
12991
|
+
await client2.mutation(api.machines.updateDaemonStatus, {
|
|
12992
|
+
sessionId: typedSessionId,
|
|
12993
|
+
machineId,
|
|
12994
|
+
connected: true
|
|
12995
|
+
});
|
|
12996
|
+
} catch (error) {
|
|
12997
|
+
console.error(`❌ Failed to update daemon status: ${error.message}`);
|
|
12998
|
+
releaseLock();
|
|
12999
|
+
process.exit(1);
|
|
13000
|
+
}
|
|
13001
|
+
const config3 = loadMachineConfig();
|
|
13002
|
+
const ctx = { client: client2, sessionId: typedSessionId, machineId, config: config3 };
|
|
13003
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
|
|
13004
|
+
console.log(` Machine ID: ${machineId}`);
|
|
13005
|
+
console.log(` Hostname: ${config3?.hostname ?? "Unknown"}`);
|
|
13006
|
+
console.log(` Available harnesses: ${config3?.availableHarnesses.join(", ") || "none"}`);
|
|
13007
|
+
console.log(` PID: ${process.pid}`);
|
|
13008
|
+
console.log(`
|
|
13009
|
+
[${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
|
|
13010
|
+
try {
|
|
13011
|
+
await recoverAgentState(ctx);
|
|
13012
|
+
} catch (e) {
|
|
13013
|
+
console.log(` ⚠️ Recovery failed: ${e.message}`);
|
|
13014
|
+
console.log(` Continuing with fresh state`);
|
|
13015
|
+
}
|
|
13016
|
+
return ctx;
|
|
13017
|
+
}
|
|
13018
|
+
async function startCommandLoop(ctx) {
|
|
13019
|
+
const shutdown = async () => {
|
|
13020
|
+
console.log(`
|
|
13021
|
+
[${formatTimestamp()}] Shutting down...`);
|
|
13022
|
+
try {
|
|
13023
|
+
await ctx.client.mutation(api.machines.updateDaemonStatus, {
|
|
13024
|
+
sessionId: ctx.sessionId,
|
|
13025
|
+
machineId: ctx.machineId,
|
|
13026
|
+
connected: false
|
|
13027
|
+
});
|
|
13028
|
+
} catch {}
|
|
13029
|
+
releaseLock();
|
|
13030
|
+
process.exit(0);
|
|
13031
|
+
};
|
|
13032
|
+
process.on("SIGINT", shutdown);
|
|
13033
|
+
process.on("SIGTERM", shutdown);
|
|
13034
|
+
process.on("SIGHUP", shutdown);
|
|
13035
|
+
const wsClient2 = await getConvexWsClient();
|
|
13036
|
+
const commandQueue = [];
|
|
13037
|
+
const queuedCommandIds = new Set;
|
|
13038
|
+
let drainingQueue = false;
|
|
13039
|
+
const enqueueCommands = (commands) => {
|
|
13040
|
+
for (const command of commands) {
|
|
13041
|
+
const commandId = command._id.toString();
|
|
13042
|
+
if (queuedCommandIds.has(commandId))
|
|
13043
|
+
continue;
|
|
13044
|
+
queuedCommandIds.add(commandId);
|
|
13045
|
+
commandQueue.push(command);
|
|
13046
|
+
}
|
|
13047
|
+
};
|
|
13048
|
+
const drainQueue = async () => {
|
|
13049
|
+
if (drainingQueue)
|
|
13050
|
+
return;
|
|
13051
|
+
drainingQueue = true;
|
|
13052
|
+
try {
|
|
13053
|
+
while (commandQueue.length > 0) {
|
|
13054
|
+
const command = commandQueue.shift();
|
|
13055
|
+
const commandId = command._id.toString();
|
|
13056
|
+
queuedCommandIds.delete(commandId);
|
|
13057
|
+
try {
|
|
13058
|
+
await processCommand(ctx, command);
|
|
13059
|
+
} catch (error) {
|
|
13060
|
+
console.error(` ❌ Command processing failed: ${error.message}`);
|
|
13061
|
+
}
|
|
13062
|
+
}
|
|
13063
|
+
} finally {
|
|
13064
|
+
drainingQueue = false;
|
|
13065
|
+
}
|
|
13066
|
+
};
|
|
13067
|
+
console.log(`
|
|
13068
|
+
Listening for commands...`);
|
|
13069
|
+
console.log(`Press Ctrl+C to stop
|
|
13070
|
+
`);
|
|
13071
|
+
wsClient2.onUpdate(api.machines.getPendingCommands, {
|
|
13072
|
+
sessionId: ctx.sessionId,
|
|
13073
|
+
machineId: ctx.machineId
|
|
13074
|
+
}, async (result) => {
|
|
13075
|
+
if (!result.commands || result.commands.length === 0)
|
|
13076
|
+
return;
|
|
13077
|
+
const parsed = result.commands.map(parseMachineCommand).filter((c) => c !== null);
|
|
13078
|
+
enqueueCommands(parsed);
|
|
13079
|
+
await drainQueue();
|
|
13080
|
+
});
|
|
13081
|
+
return await new Promise(() => {});
|
|
13082
|
+
}
|
|
13083
|
+
async function daemonStart() {
|
|
13084
|
+
const ctx = await initDaemon();
|
|
13085
|
+
await startCommandLoop(ctx);
|
|
13086
|
+
}
|
|
13087
|
+
var init_daemon_start = __esm(() => {
|
|
13088
|
+
init_pid();
|
|
13089
|
+
init_api3();
|
|
13090
|
+
init_agent_drivers();
|
|
13091
|
+
init_storage();
|
|
13092
|
+
init_client2();
|
|
13093
|
+
init_machine();
|
|
13094
|
+
});
|
|
13095
|
+
|
|
13096
|
+
// src/commands/machine/daemon-stop.ts
|
|
13097
|
+
async function daemonStop() {
|
|
13098
|
+
const { running, pid } = isDaemonRunning();
|
|
13099
|
+
if (!running) {
|
|
13100
|
+
console.log(`⚪ Daemon is not running`);
|
|
13101
|
+
return;
|
|
13102
|
+
}
|
|
13103
|
+
console.log(`Stopping daemon (PID: ${pid})...`);
|
|
13104
|
+
try {
|
|
13105
|
+
process.kill(pid, "SIGTERM");
|
|
13106
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1000));
|
|
13107
|
+
try {
|
|
13108
|
+
process.kill(pid, 0);
|
|
13109
|
+
console.log(`Process did not exit gracefully, forcing...`);
|
|
13110
|
+
process.kill(pid, "SIGKILL");
|
|
13111
|
+
} catch {}
|
|
13112
|
+
removePid();
|
|
13113
|
+
console.log(`✅ Daemon stopped`);
|
|
13114
|
+
} catch (error) {
|
|
13115
|
+
console.error(`❌ Failed to stop daemon: ${error.message}`);
|
|
13116
|
+
removePid();
|
|
13117
|
+
}
|
|
13118
|
+
}
|
|
13119
|
+
var init_daemon_stop = __esm(() => {
|
|
13120
|
+
init_pid();
|
|
13121
|
+
});
|
|
13122
|
+
|
|
13123
|
+
// src/commands/machine/daemon-status.ts
|
|
13124
|
+
async function daemonStatus() {
|
|
13125
|
+
const { running, pid } = isDaemonRunning();
|
|
13126
|
+
if (running) {
|
|
13127
|
+
console.log(`✅ Daemon is running`);
|
|
13128
|
+
console.log(` PID: ${pid}`);
|
|
13129
|
+
console.log(` PID file: ${getPidFilePath()}`);
|
|
13130
|
+
} else {
|
|
13131
|
+
console.log(`⚪ Daemon is not running`);
|
|
13132
|
+
console.log(`
|
|
13133
|
+
To start the daemon:`);
|
|
13134
|
+
console.log(` chatroom machine daemon start`);
|
|
13135
|
+
}
|
|
13136
|
+
}
|
|
13137
|
+
var init_daemon_status = __esm(() => {
|
|
13138
|
+
init_pid();
|
|
13139
|
+
});
|
|
13140
|
+
|
|
13141
|
+
// src/commands/machine/index.ts
|
|
13142
|
+
var exports_machine = {};
|
|
13143
|
+
__export(exports_machine, {
|
|
13144
|
+
daemonStop: () => daemonStop,
|
|
13145
|
+
daemonStatus: () => daemonStatus,
|
|
13146
|
+
daemonStart: () => daemonStart
|
|
13147
|
+
});
|
|
13148
|
+
var init_machine2 = __esm(() => {
|
|
13149
|
+
init_daemon_start();
|
|
13150
|
+
init_daemon_stop();
|
|
13151
|
+
init_daemon_status();
|
|
13152
|
+
});
|
|
13153
|
+
|
|
12065
13154
|
// src/commands/opencode-install.ts
|
|
12066
13155
|
var exports_opencode_install = {};
|
|
12067
13156
|
__export(exports_opencode_install, {
|
|
@@ -12069,8 +13158,8 @@ __export(exports_opencode_install, {
|
|
|
12069
13158
|
});
|
|
12070
13159
|
async function isChatroomInstalled() {
|
|
12071
13160
|
try {
|
|
12072
|
-
const { execSync } = await import("child_process");
|
|
12073
|
-
|
|
13161
|
+
const { execSync: execSync4 } = await import("child_process");
|
|
13162
|
+
execSync4("chatroom --version", { stdio: "pipe" });
|
|
12074
13163
|
return true;
|
|
12075
13164
|
} catch {
|
|
12076
13165
|
return false;
|
|
@@ -12765,8 +13854,23 @@ artifactCommand.command("view-many").description("View multiple artifacts").requ
|
|
|
12765
13854
|
artifactIds: options.artifact || []
|
|
12766
13855
|
});
|
|
12767
13856
|
});
|
|
12768
|
-
var
|
|
12769
|
-
|
|
13857
|
+
var machineCommand = program2.command("machine").description("Machine daemon management for remote agent control");
|
|
13858
|
+
var daemonCommand = machineCommand.command("daemon").description("Manage the machine daemon");
|
|
13859
|
+
daemonCommand.command("start").description("Start the machine daemon to listen for remote commands").action(async () => {
|
|
13860
|
+
await maybeRequireAuth();
|
|
13861
|
+
const { daemonStart: daemonStart2 } = await Promise.resolve().then(() => (init_machine2(), exports_machine));
|
|
13862
|
+
await daemonStart2();
|
|
13863
|
+
});
|
|
13864
|
+
daemonCommand.command("stop").description("Stop the running machine daemon").action(async () => {
|
|
13865
|
+
const { daemonStop: daemonStop2 } = await Promise.resolve().then(() => (init_machine2(), exports_machine));
|
|
13866
|
+
await daemonStop2();
|
|
13867
|
+
});
|
|
13868
|
+
daemonCommand.command("status").description("Check if the machine daemon is running").action(async () => {
|
|
13869
|
+
const { daemonStatus: daemonStatus2 } = await Promise.resolve().then(() => (init_machine2(), exports_machine));
|
|
13870
|
+
await daemonStatus2();
|
|
13871
|
+
});
|
|
13872
|
+
var opencodeCommand = program2.command("opencode").description("OpenCode integration harness");
|
|
13873
|
+
opencodeCommand.command("install").description("Install chatroom as an OpenCode harness").option("--force", "Overwrite existing harness installation").action(async (options) => {
|
|
12770
13874
|
const { installTool: installTool2 } = await Promise.resolve().then(() => exports_opencode_install);
|
|
12771
13875
|
await installTool2({ checkExisting: !options.force });
|
|
12772
13876
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatroom-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"description": "CLI for multi-agent chatroom collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"convex"
|
|
35
35
|
],
|
|
36
36
|
"author": "",
|
|
37
|
-
"license": "
|
|
37
|
+
"license": "Elastic-2.0",
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
|
40
40
|
"url": "https://github.com/conradkoh/chatroom"
|