chatroom-cli 1.0.64 → 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 +1150 -21
- 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,13 +10722,54 @@ 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;
|
|
10766
|
+
const connectionId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
10269
10767
|
await client2.mutation(api.participants.join, {
|
|
10270
10768
|
sessionId,
|
|
10271
10769
|
chatroomId,
|
|
10272
10770
|
role,
|
|
10273
|
-
readyUntil
|
|
10771
|
+
readyUntil,
|
|
10772
|
+
connectionId
|
|
10274
10773
|
});
|
|
10275
10774
|
const connectionTime = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
10276
10775
|
if (!silent) {
|
|
@@ -10326,6 +10825,29 @@ ${"─".repeat(50)}`);
|
|
|
10326
10825
|
const handlePendingTasks = async (pendingTasks) => {
|
|
10327
10826
|
if (taskProcessed)
|
|
10328
10827
|
return;
|
|
10828
|
+
const currentConnectionId = await client2.query(api.participants.getConnectionId, {
|
|
10829
|
+
sessionId,
|
|
10830
|
+
chatroomId,
|
|
10831
|
+
role
|
|
10832
|
+
});
|
|
10833
|
+
if (currentConnectionId && currentConnectionId !== connectionId) {
|
|
10834
|
+
if (unsubscribe)
|
|
10835
|
+
unsubscribe();
|
|
10836
|
+
clearTimeout(timeoutHandle);
|
|
10837
|
+
const takeoverTime = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
10838
|
+
console.log(`
|
|
10839
|
+
${"─".repeat(50)}`);
|
|
10840
|
+
console.log(`⚠️ CONNECTION SUPERSEDED
|
|
10841
|
+
`);
|
|
10842
|
+
console.log(`[${takeoverTime}] Why: Another wait-for-task process started for this role`);
|
|
10843
|
+
console.log(`Impact: This process is being replaced by the newer connection`);
|
|
10844
|
+
console.log(`Action: This is expected if you started a new wait-for-task session
|
|
10845
|
+
`);
|
|
10846
|
+
console.log(`If you meant to use THIS terminal, run:`);
|
|
10847
|
+
console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
|
|
10848
|
+
console.log(`${"─".repeat(50)}`);
|
|
10849
|
+
process.exit(0);
|
|
10850
|
+
}
|
|
10329
10851
|
const taskWithMessage = pendingTasks.length > 0 ? pendingTasks[0] : null;
|
|
10330
10852
|
if (!taskWithMessage) {
|
|
10331
10853
|
return;
|
|
@@ -10360,20 +10882,6 @@ ${"─".repeat(50)}`);
|
|
|
10360
10882
|
status: "active",
|
|
10361
10883
|
expiresAt: activeUntil
|
|
10362
10884
|
});
|
|
10363
|
-
if (message && message.type === "interrupt") {
|
|
10364
|
-
const interruptTime = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
10365
|
-
console.log(`
|
|
10366
|
-
${"─".repeat(50)}`);
|
|
10367
|
-
console.log(`⚠️ RECONNECTION REQUIRED
|
|
10368
|
-
`);
|
|
10369
|
-
console.log(`[${interruptTime}] Why: Interrupt message received from team`);
|
|
10370
|
-
console.log(`Impact: You are no longer listening for tasks`);
|
|
10371
|
-
console.log(`Action: Run this command immediately to resume availability
|
|
10372
|
-
`);
|
|
10373
|
-
console.log(waitForTaskCommand({ chatroomId, role, cliEnvPrefix }));
|
|
10374
|
-
console.log(`${"─".repeat(50)}`);
|
|
10375
|
-
process.exit(0);
|
|
10376
|
-
}
|
|
10377
10885
|
const taskDeliveryPrompt = await client2.query(api.messages.getTaskDeliveryPrompt, {
|
|
10378
10886
|
sessionId,
|
|
10379
10887
|
chatroomId,
|
|
@@ -10526,8 +11034,10 @@ var init_wait_for_task = __esm(() => {
|
|
|
10526
11034
|
init_env();
|
|
10527
11035
|
init_api3();
|
|
10528
11036
|
init_config2();
|
|
11037
|
+
init_agent_drivers();
|
|
10529
11038
|
init_storage();
|
|
10530
11039
|
init_client2();
|
|
11040
|
+
init_machine();
|
|
10531
11041
|
});
|
|
10532
11042
|
|
|
10533
11043
|
// src/commands/task-started.ts
|
|
@@ -11548,12 +12058,12 @@ __export(exports_file_content, {
|
|
|
11548
12058
|
resolveContent: () => resolveContent,
|
|
11549
12059
|
readFileContent: () => readFileContent
|
|
11550
12060
|
});
|
|
11551
|
-
import { readFileSync as
|
|
12061
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
11552
12062
|
import { resolve } from "path";
|
|
11553
12063
|
function readFileContent(filePath, optionName) {
|
|
11554
12064
|
const absolutePath = resolve(process.cwd(), filePath);
|
|
11555
12065
|
try {
|
|
11556
|
-
return
|
|
12066
|
+
return readFileSync5(absolutePath, "utf-8");
|
|
11557
12067
|
} catch (err) {
|
|
11558
12068
|
const nodeErr = err;
|
|
11559
12069
|
throw new Error(`Cannot read file for --${optionName}: ${absolutePath}
|
|
@@ -12037,6 +12547,610 @@ var init_artifact = __esm(() => {
|
|
|
12037
12547
|
init_file_content();
|
|
12038
12548
|
});
|
|
12039
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
|
+
|
|
12040
13154
|
// src/commands/opencode-install.ts
|
|
12041
13155
|
var exports_opencode_install = {};
|
|
12042
13156
|
__export(exports_opencode_install, {
|
|
@@ -12044,8 +13158,8 @@ __export(exports_opencode_install, {
|
|
|
12044
13158
|
});
|
|
12045
13159
|
async function isChatroomInstalled() {
|
|
12046
13160
|
try {
|
|
12047
|
-
const { execSync } = await import("child_process");
|
|
12048
|
-
|
|
13161
|
+
const { execSync: execSync4 } = await import("child_process");
|
|
13162
|
+
execSync4("chatroom --version", { stdio: "pipe" });
|
|
12049
13163
|
return true;
|
|
12050
13164
|
} catch {
|
|
12051
13165
|
return false;
|
|
@@ -12740,8 +13854,23 @@ artifactCommand.command("view-many").description("View multiple artifacts").requ
|
|
|
12740
13854
|
artifactIds: options.artifact || []
|
|
12741
13855
|
});
|
|
12742
13856
|
});
|
|
12743
|
-
var
|
|
12744
|
-
|
|
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) => {
|
|
12745
13874
|
const { installTool: installTool2 } = await Promise.resolve().then(() => exports_opencode_install);
|
|
12746
13875
|
await installTool2({ checkExisting: !options.force });
|
|
12747
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"
|