chatroom-cli 1.4.4 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1223 -1093
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10123,306 +10123,11 @@ var init_auth_logout = __esm(() => {
|
|
|
10123
10123
|
init_storage();
|
|
10124
10124
|
});
|
|
10125
10125
|
|
|
10126
|
-
// src/infrastructure/machine/types.ts
|
|
10127
|
-
var AGENT_HARNESSES, AGENT_HARNESS_COMMANDS, MACHINE_CONFIG_VERSION = "1";
|
|
10128
|
-
var init_types = __esm(() => {
|
|
10129
|
-
AGENT_HARNESSES = ["opencode", "pi"];
|
|
10130
|
-
AGENT_HARNESS_COMMANDS = {
|
|
10131
|
-
opencode: "opencode",
|
|
10132
|
-
pi: "pi"
|
|
10133
|
-
};
|
|
10134
|
-
});
|
|
10135
|
-
|
|
10136
|
-
// src/infrastructure/machine/detection.ts
|
|
10137
|
-
import { execSync } from "node:child_process";
|
|
10138
|
-
function commandExists(command) {
|
|
10139
|
-
try {
|
|
10140
|
-
const checkCommand = process.platform === "win32" ? `where ${command}` : `which ${command}`;
|
|
10141
|
-
execSync(checkCommand, { stdio: "ignore" });
|
|
10142
|
-
return true;
|
|
10143
|
-
} catch {
|
|
10144
|
-
return false;
|
|
10145
|
-
}
|
|
10146
|
-
}
|
|
10147
|
-
function parseVersion(versionStr) {
|
|
10148
|
-
const match = versionStr.match(/v?(\d+)\.(\d+)\.(\d+)/);
|
|
10149
|
-
if (!match)
|
|
10150
|
-
return null;
|
|
10151
|
-
const major = parseInt(match[1], 10);
|
|
10152
|
-
const version2 = `${match[1]}.${match[2]}.${match[3]}`;
|
|
10153
|
-
return { version: version2, major };
|
|
10154
|
-
}
|
|
10155
|
-
function detectHarnessVersion(harness) {
|
|
10156
|
-
const versionCommand = HARNESS_VERSION_COMMANDS[harness];
|
|
10157
|
-
if (!versionCommand)
|
|
10158
|
-
return null;
|
|
10159
|
-
try {
|
|
10160
|
-
const output = execSync(versionCommand, {
|
|
10161
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
10162
|
-
timeout: 5000
|
|
10163
|
-
}).toString().trim();
|
|
10164
|
-
return parseVersion(output);
|
|
10165
|
-
} catch {
|
|
10166
|
-
return null;
|
|
10167
|
-
}
|
|
10168
|
-
}
|
|
10169
|
-
function detectHarnessVersions(harnesses) {
|
|
10170
|
-
const versions = {};
|
|
10171
|
-
for (const harness of harnesses) {
|
|
10172
|
-
const version2 = detectHarnessVersion(harness);
|
|
10173
|
-
if (version2) {
|
|
10174
|
-
versions[harness] = version2;
|
|
10175
|
-
}
|
|
10176
|
-
}
|
|
10177
|
-
return versions;
|
|
10178
|
-
}
|
|
10179
|
-
function detectAvailableHarnesses() {
|
|
10180
|
-
const available = [];
|
|
10181
|
-
for (const harness of AGENT_HARNESSES) {
|
|
10182
|
-
const command = AGENT_HARNESS_COMMANDS[harness];
|
|
10183
|
-
if (commandExists(command)) {
|
|
10184
|
-
available.push(harness);
|
|
10185
|
-
}
|
|
10186
|
-
}
|
|
10187
|
-
return available;
|
|
10188
|
-
}
|
|
10189
|
-
var HARNESS_VERSION_COMMANDS;
|
|
10190
|
-
var init_detection = __esm(() => {
|
|
10191
|
-
init_types();
|
|
10192
|
-
HARNESS_VERSION_COMMANDS = {
|
|
10193
|
-
opencode: "opencode --version",
|
|
10194
|
-
pi: "pi --version"
|
|
10195
|
-
};
|
|
10196
|
-
});
|
|
10197
|
-
|
|
10198
|
-
// src/infrastructure/machine/storage.ts
|
|
10199
|
-
import { randomUUID } from "node:crypto";
|
|
10200
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "node:fs";
|
|
10201
|
-
import { homedir as homedir2, hostname as hostname2 } from "node:os";
|
|
10202
|
-
import { join as join3 } from "node:path";
|
|
10203
|
-
function ensureConfigDir2() {
|
|
10204
|
-
if (!existsSync2(CHATROOM_DIR2)) {
|
|
10205
|
-
mkdirSync2(CHATROOM_DIR2, { recursive: true, mode: 448 });
|
|
10206
|
-
}
|
|
10207
|
-
}
|
|
10208
|
-
function getMachineConfigPath() {
|
|
10209
|
-
return join3(CHATROOM_DIR2, MACHINE_FILE);
|
|
10210
|
-
}
|
|
10211
|
-
function loadConfigFile() {
|
|
10212
|
-
const configPath = getMachineConfigPath();
|
|
10213
|
-
if (!existsSync2(configPath)) {
|
|
10214
|
-
return null;
|
|
10215
|
-
}
|
|
10216
|
-
try {
|
|
10217
|
-
const content = readFileSync3(configPath, "utf-8");
|
|
10218
|
-
return JSON.parse(content);
|
|
10219
|
-
} catch (error) {
|
|
10220
|
-
console.warn(`⚠️ Failed to read machine config at ${configPath}: ${error.message}`);
|
|
10221
|
-
console.warn(` The machine will re-register with a new identity on next startup.`);
|
|
10222
|
-
console.warn(` If this is unexpected, check the file for corruption.`);
|
|
10223
|
-
return null;
|
|
10224
|
-
}
|
|
10225
|
-
}
|
|
10226
|
-
function saveConfigFile(configFile) {
|
|
10227
|
-
ensureConfigDir2();
|
|
10228
|
-
const configPath = getMachineConfigPath();
|
|
10229
|
-
const tempPath = `${configPath}.tmp`;
|
|
10230
|
-
const content = JSON.stringify(configFile, null, 2);
|
|
10231
|
-
writeFileSync2(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
10232
|
-
renameSync(tempPath, configPath);
|
|
10233
|
-
}
|
|
10234
|
-
function loadMachineConfig() {
|
|
10235
|
-
const configFile = loadConfigFile();
|
|
10236
|
-
if (!configFile)
|
|
10237
|
-
return null;
|
|
10238
|
-
const convexUrl = getConvexUrl();
|
|
10239
|
-
return configFile.machines[convexUrl] ?? null;
|
|
10240
|
-
}
|
|
10241
|
-
function saveMachineConfig(config) {
|
|
10242
|
-
const configFile = loadConfigFile() ?? {
|
|
10243
|
-
version: MACHINE_CONFIG_VERSION,
|
|
10244
|
-
machines: {}
|
|
10245
|
-
};
|
|
10246
|
-
const convexUrl = getConvexUrl();
|
|
10247
|
-
configFile.machines[convexUrl] = config;
|
|
10248
|
-
saveConfigFile(configFile);
|
|
10249
|
-
}
|
|
10250
|
-
function createNewEndpointConfig() {
|
|
10251
|
-
const now = new Date().toISOString();
|
|
10252
|
-
const availableHarnesses = detectAvailableHarnesses();
|
|
10253
|
-
return {
|
|
10254
|
-
machineId: randomUUID(),
|
|
10255
|
-
hostname: hostname2(),
|
|
10256
|
-
os: process.platform,
|
|
10257
|
-
registeredAt: now,
|
|
10258
|
-
lastSyncedAt: now,
|
|
10259
|
-
availableHarnesses,
|
|
10260
|
-
harnessVersions: detectHarnessVersions(availableHarnesses)
|
|
10261
|
-
};
|
|
10262
|
-
}
|
|
10263
|
-
function ensureMachineRegistered() {
|
|
10264
|
-
let config = loadMachineConfig();
|
|
10265
|
-
if (!config) {
|
|
10266
|
-
config = createNewEndpointConfig();
|
|
10267
|
-
saveMachineConfig(config);
|
|
10268
|
-
} else {
|
|
10269
|
-
const now = new Date().toISOString();
|
|
10270
|
-
config.availableHarnesses = detectAvailableHarnesses();
|
|
10271
|
-
config.harnessVersions = detectHarnessVersions(config.availableHarnesses);
|
|
10272
|
-
config.lastSyncedAt = now;
|
|
10273
|
-
saveMachineConfig(config);
|
|
10274
|
-
}
|
|
10275
|
-
return {
|
|
10276
|
-
machineId: config.machineId,
|
|
10277
|
-
hostname: config.hostname,
|
|
10278
|
-
os: config.os,
|
|
10279
|
-
availableHarnesses: config.availableHarnesses,
|
|
10280
|
-
harnessVersions: config.harnessVersions
|
|
10281
|
-
};
|
|
10282
|
-
}
|
|
10283
|
-
var CHATROOM_DIR2, MACHINE_FILE = "machine.json";
|
|
10284
|
-
var init_storage2 = __esm(() => {
|
|
10285
|
-
init_detection();
|
|
10286
|
-
init_types();
|
|
10287
|
-
init_client2();
|
|
10288
|
-
CHATROOM_DIR2 = join3(homedir2(), ".chatroom");
|
|
10289
|
-
});
|
|
10290
|
-
|
|
10291
|
-
// src/infrastructure/machine/daemon-state.ts
|
|
10292
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2 } from "node:fs";
|
|
10293
|
-
import { homedir as homedir3 } from "node:os";
|
|
10294
|
-
import { join as join4 } from "node:path";
|
|
10295
|
-
function agentKey(chatroomId, role) {
|
|
10296
|
-
return `${chatroomId}/${role}`;
|
|
10297
|
-
}
|
|
10298
|
-
function ensureStateDir() {
|
|
10299
|
-
if (!existsSync3(STATE_DIR)) {
|
|
10300
|
-
mkdirSync3(STATE_DIR, { recursive: true, mode: 448 });
|
|
10301
|
-
}
|
|
10302
|
-
}
|
|
10303
|
-
function stateFilePath(machineId) {
|
|
10304
|
-
return join4(STATE_DIR, `${machineId}.json`);
|
|
10305
|
-
}
|
|
10306
|
-
function loadDaemonState(machineId) {
|
|
10307
|
-
const filePath = stateFilePath(machineId);
|
|
10308
|
-
if (!existsSync3(filePath)) {
|
|
10309
|
-
return null;
|
|
10310
|
-
}
|
|
10311
|
-
try {
|
|
10312
|
-
const content = readFileSync4(filePath, "utf-8");
|
|
10313
|
-
return JSON.parse(content);
|
|
10314
|
-
} catch {
|
|
10315
|
-
return null;
|
|
10316
|
-
}
|
|
10317
|
-
}
|
|
10318
|
-
function saveDaemonState(state) {
|
|
10319
|
-
ensureStateDir();
|
|
10320
|
-
const filePath = stateFilePath(state.machineId);
|
|
10321
|
-
const tempPath = `${filePath}.tmp`;
|
|
10322
|
-
const content = JSON.stringify(state, null, 2);
|
|
10323
|
-
writeFileSync3(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
10324
|
-
renameSync2(tempPath, filePath);
|
|
10325
|
-
}
|
|
10326
|
-
function loadOrCreate(machineId) {
|
|
10327
|
-
return loadDaemonState(machineId) ?? {
|
|
10328
|
-
version: STATE_VERSION,
|
|
10329
|
-
machineId,
|
|
10330
|
-
updatedAt: new Date().toISOString(),
|
|
10331
|
-
agents: {}
|
|
10332
|
-
};
|
|
10333
|
-
}
|
|
10334
|
-
function persistAgentPid(machineId, chatroomId, role, pid, harness) {
|
|
10335
|
-
const state = loadOrCreate(machineId);
|
|
10336
|
-
state.agents[agentKey(chatroomId, role)] = {
|
|
10337
|
-
pid,
|
|
10338
|
-
harness,
|
|
10339
|
-
startedAt: new Date().toISOString()
|
|
10340
|
-
};
|
|
10341
|
-
state.updatedAt = new Date().toISOString();
|
|
10342
|
-
saveDaemonState(state);
|
|
10343
|
-
}
|
|
10344
|
-
function clearAgentPid(machineId, chatroomId, role) {
|
|
10345
|
-
const state = loadDaemonState(machineId);
|
|
10346
|
-
if (!state)
|
|
10347
|
-
return;
|
|
10348
|
-
const key = agentKey(chatroomId, role);
|
|
10349
|
-
if (!(key in state.agents))
|
|
10350
|
-
return;
|
|
10351
|
-
delete state.agents[key];
|
|
10352
|
-
state.updatedAt = new Date().toISOString();
|
|
10353
|
-
saveDaemonState(state);
|
|
10354
|
-
}
|
|
10355
|
-
function listAgentEntries(machineId) {
|
|
10356
|
-
const state = loadDaemonState(machineId);
|
|
10357
|
-
if (!state)
|
|
10358
|
-
return [];
|
|
10359
|
-
const results = [];
|
|
10360
|
-
for (const [key, entry] of Object.entries(state.agents)) {
|
|
10361
|
-
const separatorIndex = key.lastIndexOf("/");
|
|
10362
|
-
if (separatorIndex === -1)
|
|
10363
|
-
continue;
|
|
10364
|
-
const chatroomId = key.substring(0, separatorIndex);
|
|
10365
|
-
const role = key.substring(separatorIndex + 1);
|
|
10366
|
-
results.push({ chatroomId, role, entry });
|
|
10367
|
-
}
|
|
10368
|
-
return results;
|
|
10369
|
-
}
|
|
10370
|
-
function persistEventCursor(machineId, lastSeenEventId) {
|
|
10371
|
-
try {
|
|
10372
|
-
const state = loadOrCreate(machineId);
|
|
10373
|
-
state.lastSeenEventId = lastSeenEventId;
|
|
10374
|
-
state.updatedAt = new Date().toISOString();
|
|
10375
|
-
saveDaemonState(state);
|
|
10376
|
-
} catch (err) {
|
|
10377
|
-
console.warn(`⚠️ Failed to persist event cursor: ${err.message}`);
|
|
10378
|
-
}
|
|
10379
|
-
}
|
|
10380
|
-
function loadEventCursor(machineId) {
|
|
10381
|
-
const state = loadDaemonState(machineId);
|
|
10382
|
-
return state?.lastSeenEventId ?? null;
|
|
10383
|
-
}
|
|
10384
|
-
var CHATROOM_DIR3, STATE_DIR, STATE_VERSION = "1";
|
|
10385
|
-
var init_daemon_state = __esm(() => {
|
|
10386
|
-
CHATROOM_DIR3 = join4(homedir3(), ".chatroom");
|
|
10387
|
-
STATE_DIR = join4(CHATROOM_DIR3, "machines", "state");
|
|
10388
|
-
});
|
|
10389
|
-
|
|
10390
|
-
// src/infrastructure/machine/intentional-stops.ts
|
|
10391
|
-
function agentKey2(chatroomId, role) {
|
|
10392
|
-
return `${chatroomId}:${role.toLowerCase()}`;
|
|
10393
|
-
}
|
|
10394
|
-
function markIntentionalStop(chatroomId, role, reason = "intentional_stop") {
|
|
10395
|
-
pendingStops.set(agentKey2(chatroomId, role), reason);
|
|
10396
|
-
}
|
|
10397
|
-
function consumeIntentionalStop(chatroomId, role) {
|
|
10398
|
-
const key = agentKey2(chatroomId, role);
|
|
10399
|
-
const reason = pendingStops.get(key) ?? null;
|
|
10400
|
-
if (reason !== null) {
|
|
10401
|
-
pendingStops.delete(key);
|
|
10402
|
-
}
|
|
10403
|
-
return reason;
|
|
10404
|
-
}
|
|
10405
|
-
function clearIntentionalStop(chatroomId, role) {
|
|
10406
|
-
pendingStops.delete(agentKey2(chatroomId, role));
|
|
10407
|
-
}
|
|
10408
|
-
var pendingStops;
|
|
10409
|
-
var init_intentional_stops = __esm(() => {
|
|
10410
|
-
pendingStops = new Map;
|
|
10411
|
-
});
|
|
10412
|
-
|
|
10413
|
-
// src/infrastructure/machine/index.ts
|
|
10414
|
-
var init_machine = __esm(() => {
|
|
10415
|
-
init_types();
|
|
10416
|
-
init_storage2();
|
|
10417
|
-
init_daemon_state();
|
|
10418
|
-
init_intentional_stops();
|
|
10419
|
-
});
|
|
10420
|
-
|
|
10421
10126
|
// src/infrastructure/services/remote-agents/base-cli-agent-service.ts
|
|
10422
|
-
import { spawn, execSync
|
|
10127
|
+
import { spawn, execSync } from "node:child_process";
|
|
10423
10128
|
function defaultDeps() {
|
|
10424
10129
|
return {
|
|
10425
|
-
execSync
|
|
10130
|
+
execSync,
|
|
10426
10131
|
spawn,
|
|
10427
10132
|
kill: (pid, signal) => process.kill(pid, signal)
|
|
10428
10133
|
};
|
|
@@ -10514,6 +10219,9 @@ var OPENCODE_COMMAND = "opencode", OpenCodeAgentService;
|
|
|
10514
10219
|
var init_opencode_agent_service = __esm(() => {
|
|
10515
10220
|
init_base_cli_agent_service();
|
|
10516
10221
|
OpenCodeAgentService = class OpenCodeAgentService extends BaseCLIAgentService {
|
|
10222
|
+
id = "opencode";
|
|
10223
|
+
displayName = "OpenCode";
|
|
10224
|
+
command = OPENCODE_COMMAND;
|
|
10517
10225
|
constructor(deps) {
|
|
10518
10226
|
super(deps);
|
|
10519
10227
|
}
|
|
@@ -10597,10 +10305,6 @@ ${options.prompt}` : options.prompt;
|
|
|
10597
10305
|
});
|
|
10598
10306
|
|
|
10599
10307
|
// src/infrastructure/services/remote-agents/opencode/index.ts
|
|
10600
|
-
var exports_opencode = {};
|
|
10601
|
-
__export(exports_opencode, {
|
|
10602
|
-
OpenCodeAgentService: () => OpenCodeAgentService
|
|
10603
|
-
});
|
|
10604
10308
|
var init_opencode = __esm(() => {
|
|
10605
10309
|
init_opencode_agent_service();
|
|
10606
10310
|
});
|
|
@@ -10700,6 +10404,9 @@ var init_pi_agent_service = __esm(() => {
|
|
|
10700
10404
|
init_base_cli_agent_service();
|
|
10701
10405
|
init_pi_rpc_reader();
|
|
10702
10406
|
PiAgentService = class PiAgentService extends BaseCLIAgentService {
|
|
10407
|
+
id = "pi";
|
|
10408
|
+
displayName = "Pi";
|
|
10409
|
+
command = PI_COMMAND;
|
|
10703
10410
|
constructor(deps) {
|
|
10704
10411
|
super(deps);
|
|
10705
10412
|
}
|
|
@@ -10886,13 +10593,528 @@ var init_pi_agent_service = __esm(() => {
|
|
|
10886
10593
|
};
|
|
10887
10594
|
});
|
|
10888
10595
|
|
|
10889
|
-
// src/infrastructure/services/remote-agents/pi/index.ts
|
|
10890
|
-
var
|
|
10891
|
-
|
|
10892
|
-
|
|
10596
|
+
// src/infrastructure/services/remote-agents/pi/index.ts
|
|
10597
|
+
var init_pi = __esm(() => {
|
|
10598
|
+
init_pi_agent_service();
|
|
10599
|
+
});
|
|
10600
|
+
|
|
10601
|
+
// src/infrastructure/services/remote-agents/cursor/cursor-stream-reader.ts
|
|
10602
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
10603
|
+
|
|
10604
|
+
class CursorStreamReader {
|
|
10605
|
+
textCallbacks = [];
|
|
10606
|
+
agentEndCallbacks = [];
|
|
10607
|
+
toolCallCallbacks = [];
|
|
10608
|
+
toolResultCallbacks = [];
|
|
10609
|
+
anyEventCallbacks = [];
|
|
10610
|
+
constructor(stream) {
|
|
10611
|
+
const rl = createInterface2({ input: stream, crlfDelay: Infinity });
|
|
10612
|
+
rl.on("line", (line) => this._handleLine(line));
|
|
10613
|
+
}
|
|
10614
|
+
onText(cb) {
|
|
10615
|
+
this.textCallbacks.push(cb);
|
|
10616
|
+
}
|
|
10617
|
+
onAgentEnd(cb) {
|
|
10618
|
+
this.agentEndCallbacks.push(cb);
|
|
10619
|
+
}
|
|
10620
|
+
onToolCall(cb) {
|
|
10621
|
+
this.toolCallCallbacks.push(cb);
|
|
10622
|
+
}
|
|
10623
|
+
onToolResult(cb) {
|
|
10624
|
+
this.toolResultCallbacks.push(cb);
|
|
10625
|
+
}
|
|
10626
|
+
onAnyEvent(cb) {
|
|
10627
|
+
this.anyEventCallbacks.push(cb);
|
|
10628
|
+
}
|
|
10629
|
+
_handleLine(line) {
|
|
10630
|
+
const trimmed = line.trim();
|
|
10631
|
+
if (!trimmed)
|
|
10632
|
+
return;
|
|
10633
|
+
let event;
|
|
10634
|
+
try {
|
|
10635
|
+
event = JSON.parse(trimmed);
|
|
10636
|
+
} catch {
|
|
10637
|
+
return;
|
|
10638
|
+
}
|
|
10639
|
+
for (const cb of this.anyEventCallbacks)
|
|
10640
|
+
cb();
|
|
10641
|
+
const type = event["type"];
|
|
10642
|
+
const subtype = event["subtype"];
|
|
10643
|
+
if (type === "assistant") {
|
|
10644
|
+
const message = event["message"];
|
|
10645
|
+
const content = message?.["content"] ?? [];
|
|
10646
|
+
for (const block of content) {
|
|
10647
|
+
if (block["type"] === "text" && typeof block["text"] === "string") {
|
|
10648
|
+
for (const cb of this.textCallbacks)
|
|
10649
|
+
cb(block["text"]);
|
|
10650
|
+
}
|
|
10651
|
+
}
|
|
10652
|
+
return;
|
|
10653
|
+
}
|
|
10654
|
+
if (type === "tool_call") {
|
|
10655
|
+
const callId = event["call_id"] ?? "";
|
|
10656
|
+
const toolCall = event["tool_call"];
|
|
10657
|
+
if (subtype === "started") {
|
|
10658
|
+
for (const cb of this.toolCallCallbacks)
|
|
10659
|
+
cb(callId, toolCall);
|
|
10660
|
+
} else if (subtype === "completed") {
|
|
10661
|
+
for (const cb of this.toolResultCallbacks)
|
|
10662
|
+
cb(callId, toolCall);
|
|
10663
|
+
}
|
|
10664
|
+
return;
|
|
10665
|
+
}
|
|
10666
|
+
if (type === "result" && subtype === "success") {
|
|
10667
|
+
const sessionId = event["session_id"];
|
|
10668
|
+
for (const cb of this.agentEndCallbacks)
|
|
10669
|
+
cb(sessionId);
|
|
10670
|
+
return;
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
10673
|
+
}
|
|
10674
|
+
var init_cursor_stream_reader = () => {};
|
|
10675
|
+
|
|
10676
|
+
// src/infrastructure/services/remote-agents/cursor/cursor-agent-service.ts
|
|
10677
|
+
var CURSOR_COMMAND = "agent", CURSOR_MODELS, CursorAgentService;
|
|
10678
|
+
var init_cursor_agent_service = __esm(() => {
|
|
10679
|
+
init_base_cli_agent_service();
|
|
10680
|
+
init_cursor_stream_reader();
|
|
10681
|
+
CURSOR_MODELS = ["opus-4.6", "sonnet-4.6"];
|
|
10682
|
+
CursorAgentService = class CursorAgentService extends BaseCLIAgentService {
|
|
10683
|
+
id = "cursor";
|
|
10684
|
+
displayName = "Cursor";
|
|
10685
|
+
command = CURSOR_COMMAND;
|
|
10686
|
+
constructor(deps) {
|
|
10687
|
+
super(deps);
|
|
10688
|
+
}
|
|
10689
|
+
isInstalled() {
|
|
10690
|
+
return this.checkInstalled(CURSOR_COMMAND);
|
|
10691
|
+
}
|
|
10692
|
+
getVersion() {
|
|
10693
|
+
return this.checkVersion(CURSOR_COMMAND);
|
|
10694
|
+
}
|
|
10695
|
+
async listModels() {
|
|
10696
|
+
return CURSOR_MODELS;
|
|
10697
|
+
}
|
|
10698
|
+
async spawn(options) {
|
|
10699
|
+
const args = ["-p", "--force", "--output-format", "stream-json"];
|
|
10700
|
+
if (options.model) {
|
|
10701
|
+
args.push("--model", options.model);
|
|
10702
|
+
}
|
|
10703
|
+
const fullPrompt = options.systemPrompt ? `${options.systemPrompt}
|
|
10704
|
+
|
|
10705
|
+
${options.prompt}` : options.prompt;
|
|
10706
|
+
const childProcess = this.deps.spawn(CURSOR_COMMAND, args, {
|
|
10707
|
+
cwd: options.workingDir,
|
|
10708
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10709
|
+
shell: false,
|
|
10710
|
+
detached: true,
|
|
10711
|
+
env: { ...process.env }
|
|
10712
|
+
});
|
|
10713
|
+
childProcess.stdin?.write(fullPrompt);
|
|
10714
|
+
childProcess.stdin?.end();
|
|
10715
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
10716
|
+
if (childProcess.killed || childProcess.exitCode !== null) {
|
|
10717
|
+
throw new Error(`Agent process exited immediately (exit code: ${childProcess.exitCode})`);
|
|
10718
|
+
}
|
|
10719
|
+
if (!childProcess.pid) {
|
|
10720
|
+
throw new Error("Agent process started but has no PID");
|
|
10721
|
+
}
|
|
10722
|
+
const pid = childProcess.pid;
|
|
10723
|
+
const context = options.context;
|
|
10724
|
+
const entry = this.registerProcess(pid, context);
|
|
10725
|
+
const roleTag = context.role ?? "unknown";
|
|
10726
|
+
const chatroomSuffix = context.chatroomId ? `@${context.chatroomId.slice(-6)}` : "";
|
|
10727
|
+
const logPrefix = `[cursor:${roleTag}${chatroomSuffix}`;
|
|
10728
|
+
const outputCallbacks = [];
|
|
10729
|
+
if (childProcess.stdout) {
|
|
10730
|
+
const reader = new CursorStreamReader(childProcess.stdout);
|
|
10731
|
+
let textBuffer = "";
|
|
10732
|
+
const flushText = () => {
|
|
10733
|
+
if (!textBuffer)
|
|
10734
|
+
return;
|
|
10735
|
+
for (const line of textBuffer.split(`
|
|
10736
|
+
`)) {
|
|
10737
|
+
if (line)
|
|
10738
|
+
process.stdout.write(`${logPrefix} text] ${line}
|
|
10739
|
+
`);
|
|
10740
|
+
}
|
|
10741
|
+
textBuffer = "";
|
|
10742
|
+
};
|
|
10743
|
+
reader.onText((text) => {
|
|
10744
|
+
textBuffer += text;
|
|
10745
|
+
if (textBuffer.includes(`
|
|
10746
|
+
`))
|
|
10747
|
+
flushText();
|
|
10748
|
+
entry.lastOutputAt = Date.now();
|
|
10749
|
+
for (const cb of outputCallbacks)
|
|
10750
|
+
cb();
|
|
10751
|
+
});
|
|
10752
|
+
reader.onAnyEvent(() => {
|
|
10753
|
+
entry.lastOutputAt = Date.now();
|
|
10754
|
+
for (const cb of outputCallbacks)
|
|
10755
|
+
cb();
|
|
10756
|
+
});
|
|
10757
|
+
reader.onAgentEnd(() => {
|
|
10758
|
+
flushText();
|
|
10759
|
+
process.stdout.write(`${logPrefix} agent_end]
|
|
10760
|
+
`);
|
|
10761
|
+
});
|
|
10762
|
+
reader.onToolCall((callId, toolCall) => {
|
|
10763
|
+
flushText();
|
|
10764
|
+
process.stdout.write(`${logPrefix} tool: ${callId} ${JSON.stringify(toolCall)}]
|
|
10765
|
+
`);
|
|
10766
|
+
});
|
|
10767
|
+
reader.onToolResult((callId) => {
|
|
10768
|
+
flushText();
|
|
10769
|
+
process.stdout.write(`${logPrefix} tool_result: ${callId}]
|
|
10770
|
+
`);
|
|
10771
|
+
});
|
|
10772
|
+
if (childProcess.stderr) {
|
|
10773
|
+
childProcess.stderr.pipe(process.stderr, { end: false });
|
|
10774
|
+
childProcess.stderr.on("data", () => {
|
|
10775
|
+
entry.lastOutputAt = Date.now();
|
|
10776
|
+
for (const cb of outputCallbacks)
|
|
10777
|
+
cb();
|
|
10778
|
+
});
|
|
10779
|
+
}
|
|
10780
|
+
return {
|
|
10781
|
+
pid,
|
|
10782
|
+
onExit: (cb) => {
|
|
10783
|
+
childProcess.on("exit", (code2, signal) => {
|
|
10784
|
+
this.deleteProcess(pid);
|
|
10785
|
+
cb({ code: code2, signal, context });
|
|
10786
|
+
});
|
|
10787
|
+
},
|
|
10788
|
+
onOutput: (cb) => {
|
|
10789
|
+
outputCallbacks.push(cb);
|
|
10790
|
+
},
|
|
10791
|
+
onAgentEnd: (cb) => {
|
|
10792
|
+
reader.onAgentEnd(cb);
|
|
10793
|
+
}
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10796
|
+
if (childProcess.stderr) {
|
|
10797
|
+
childProcess.stderr.pipe(process.stderr, { end: false });
|
|
10798
|
+
childProcess.stderr.on("data", () => {
|
|
10799
|
+
entry.lastOutputAt = Date.now();
|
|
10800
|
+
for (const cb of outputCallbacks)
|
|
10801
|
+
cb();
|
|
10802
|
+
});
|
|
10803
|
+
}
|
|
10804
|
+
return {
|
|
10805
|
+
pid,
|
|
10806
|
+
onExit: (cb) => {
|
|
10807
|
+
childProcess.on("exit", (code2, signal) => {
|
|
10808
|
+
this.deleteProcess(pid);
|
|
10809
|
+
cb({ code: code2, signal, context });
|
|
10810
|
+
});
|
|
10811
|
+
},
|
|
10812
|
+
onOutput: (cb) => {
|
|
10813
|
+
outputCallbacks.push(cb);
|
|
10814
|
+
}
|
|
10815
|
+
};
|
|
10816
|
+
}
|
|
10817
|
+
};
|
|
10818
|
+
});
|
|
10819
|
+
|
|
10820
|
+
// src/infrastructure/services/remote-agents/cursor/index.ts
|
|
10821
|
+
var init_cursor = __esm(() => {
|
|
10822
|
+
init_cursor_agent_service();
|
|
10823
|
+
});
|
|
10824
|
+
|
|
10825
|
+
// src/infrastructure/services/remote-agents/registry.ts
|
|
10826
|
+
function registerHarness(service) {
|
|
10827
|
+
registry.set(service.id, service);
|
|
10828
|
+
}
|
|
10829
|
+
function getHarness(id) {
|
|
10830
|
+
return registry.get(id);
|
|
10831
|
+
}
|
|
10832
|
+
function getAllHarnesses() {
|
|
10833
|
+
return [...registry.values()];
|
|
10834
|
+
}
|
|
10835
|
+
var registry;
|
|
10836
|
+
var init_registry = __esm(() => {
|
|
10837
|
+
registry = new Map;
|
|
10838
|
+
});
|
|
10839
|
+
|
|
10840
|
+
// src/infrastructure/services/remote-agents/init-registry.ts
|
|
10841
|
+
function initHarnessRegistry() {
|
|
10842
|
+
if (initialized)
|
|
10843
|
+
return;
|
|
10844
|
+
registerHarness(new OpenCodeAgentService);
|
|
10845
|
+
registerHarness(new PiAgentService);
|
|
10846
|
+
registerHarness(new CursorAgentService);
|
|
10847
|
+
initialized = true;
|
|
10848
|
+
}
|
|
10849
|
+
var initialized = false;
|
|
10850
|
+
var init_init_registry = __esm(() => {
|
|
10851
|
+
init_registry();
|
|
10852
|
+
init_opencode();
|
|
10853
|
+
init_pi();
|
|
10854
|
+
init_cursor();
|
|
10855
|
+
});
|
|
10856
|
+
|
|
10857
|
+
// src/infrastructure/services/remote-agents/index.ts
|
|
10858
|
+
var init_remote_agents = __esm(() => {
|
|
10859
|
+
init_opencode();
|
|
10860
|
+
init_pi();
|
|
10861
|
+
init_cursor();
|
|
10862
|
+
init_registry();
|
|
10863
|
+
init_init_registry();
|
|
10864
|
+
});
|
|
10865
|
+
|
|
10866
|
+
// src/infrastructure/machine/detection.ts
|
|
10867
|
+
function detectHarnessVersion(harness) {
|
|
10868
|
+
initHarnessRegistry();
|
|
10869
|
+
const info = getHarness(harness)?.getVersion();
|
|
10870
|
+
if (!info)
|
|
10871
|
+
return null;
|
|
10872
|
+
return { version: info.version, major: info.major };
|
|
10873
|
+
}
|
|
10874
|
+
function detectHarnessVersions(harnesses) {
|
|
10875
|
+
const versions = {};
|
|
10876
|
+
for (const harness of harnesses) {
|
|
10877
|
+
const version2 = detectHarnessVersion(harness);
|
|
10878
|
+
if (version2) {
|
|
10879
|
+
versions[harness] = version2;
|
|
10880
|
+
}
|
|
10881
|
+
}
|
|
10882
|
+
return versions;
|
|
10883
|
+
}
|
|
10884
|
+
function detectAvailableHarnesses() {
|
|
10885
|
+
initHarnessRegistry();
|
|
10886
|
+
return getAllHarnesses().filter((s) => s.isInstalled()).map((s) => s.id);
|
|
10887
|
+
}
|
|
10888
|
+
var init_detection = __esm(() => {
|
|
10889
|
+
init_remote_agents();
|
|
10890
|
+
});
|
|
10891
|
+
|
|
10892
|
+
// src/infrastructure/machine/types.ts
|
|
10893
|
+
var MACHINE_CONFIG_VERSION = "1";
|
|
10894
|
+
|
|
10895
|
+
// src/infrastructure/machine/storage.ts
|
|
10896
|
+
import { randomUUID } from "node:crypto";
|
|
10897
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "node:fs";
|
|
10898
|
+
import { homedir as homedir2, hostname as hostname2 } from "node:os";
|
|
10899
|
+
import { join as join3 } from "node:path";
|
|
10900
|
+
function ensureConfigDir2() {
|
|
10901
|
+
if (!existsSync2(CHATROOM_DIR2)) {
|
|
10902
|
+
mkdirSync2(CHATROOM_DIR2, { recursive: true, mode: 448 });
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10905
|
+
function getMachineConfigPath() {
|
|
10906
|
+
return join3(CHATROOM_DIR2, MACHINE_FILE);
|
|
10907
|
+
}
|
|
10908
|
+
function loadConfigFile() {
|
|
10909
|
+
const configPath = getMachineConfigPath();
|
|
10910
|
+
if (!existsSync2(configPath)) {
|
|
10911
|
+
return null;
|
|
10912
|
+
}
|
|
10913
|
+
try {
|
|
10914
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
10915
|
+
return JSON.parse(content);
|
|
10916
|
+
} catch (error) {
|
|
10917
|
+
console.warn(`⚠️ Failed to read machine config at ${configPath}: ${error.message}`);
|
|
10918
|
+
console.warn(` The machine will re-register with a new identity on next startup.`);
|
|
10919
|
+
console.warn(` If this is unexpected, check the file for corruption.`);
|
|
10920
|
+
return null;
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10923
|
+
function saveConfigFile(configFile) {
|
|
10924
|
+
ensureConfigDir2();
|
|
10925
|
+
const configPath = getMachineConfigPath();
|
|
10926
|
+
const tempPath = `${configPath}.tmp`;
|
|
10927
|
+
const content = JSON.stringify(configFile, null, 2);
|
|
10928
|
+
writeFileSync2(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
10929
|
+
renameSync(tempPath, configPath);
|
|
10930
|
+
}
|
|
10931
|
+
function loadMachineConfig() {
|
|
10932
|
+
const configFile = loadConfigFile();
|
|
10933
|
+
if (!configFile)
|
|
10934
|
+
return null;
|
|
10935
|
+
const convexUrl = getConvexUrl();
|
|
10936
|
+
return configFile.machines[convexUrl] ?? null;
|
|
10937
|
+
}
|
|
10938
|
+
function saveMachineConfig(config) {
|
|
10939
|
+
const configFile = loadConfigFile() ?? {
|
|
10940
|
+
version: MACHINE_CONFIG_VERSION,
|
|
10941
|
+
machines: {}
|
|
10942
|
+
};
|
|
10943
|
+
const convexUrl = getConvexUrl();
|
|
10944
|
+
configFile.machines[convexUrl] = config;
|
|
10945
|
+
saveConfigFile(configFile);
|
|
10946
|
+
}
|
|
10947
|
+
function createNewEndpointConfig() {
|
|
10948
|
+
const now = new Date().toISOString();
|
|
10949
|
+
const availableHarnesses = detectAvailableHarnesses();
|
|
10950
|
+
return {
|
|
10951
|
+
machineId: randomUUID(),
|
|
10952
|
+
hostname: hostname2(),
|
|
10953
|
+
os: process.platform,
|
|
10954
|
+
registeredAt: now,
|
|
10955
|
+
lastSyncedAt: now,
|
|
10956
|
+
availableHarnesses,
|
|
10957
|
+
harnessVersions: detectHarnessVersions(availableHarnesses)
|
|
10958
|
+
};
|
|
10959
|
+
}
|
|
10960
|
+
function ensureMachineRegistered() {
|
|
10961
|
+
let config = loadMachineConfig();
|
|
10962
|
+
if (!config) {
|
|
10963
|
+
config = createNewEndpointConfig();
|
|
10964
|
+
saveMachineConfig(config);
|
|
10965
|
+
} else {
|
|
10966
|
+
const now = new Date().toISOString();
|
|
10967
|
+
config.availableHarnesses = detectAvailableHarnesses();
|
|
10968
|
+
config.harnessVersions = detectHarnessVersions(config.availableHarnesses);
|
|
10969
|
+
config.lastSyncedAt = now;
|
|
10970
|
+
saveMachineConfig(config);
|
|
10971
|
+
}
|
|
10972
|
+
return {
|
|
10973
|
+
machineId: config.machineId,
|
|
10974
|
+
hostname: config.hostname,
|
|
10975
|
+
os: config.os,
|
|
10976
|
+
availableHarnesses: config.availableHarnesses,
|
|
10977
|
+
harnessVersions: config.harnessVersions
|
|
10978
|
+
};
|
|
10979
|
+
}
|
|
10980
|
+
function getMachineId() {
|
|
10981
|
+
const config = loadMachineConfig();
|
|
10982
|
+
return config?.machineId ?? null;
|
|
10983
|
+
}
|
|
10984
|
+
var CHATROOM_DIR2, MACHINE_FILE = "machine.json";
|
|
10985
|
+
var init_storage2 = __esm(() => {
|
|
10986
|
+
init_detection();
|
|
10987
|
+
init_client2();
|
|
10988
|
+
CHATROOM_DIR2 = join3(homedir2(), ".chatroom");
|
|
10989
|
+
});
|
|
10990
|
+
|
|
10991
|
+
// src/infrastructure/machine/daemon-state.ts
|
|
10992
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2 } from "node:fs";
|
|
10993
|
+
import { homedir as homedir3 } from "node:os";
|
|
10994
|
+
import { join as join4 } from "node:path";
|
|
10995
|
+
function agentKey(chatroomId, role) {
|
|
10996
|
+
return `${chatroomId}/${role}`;
|
|
10997
|
+
}
|
|
10998
|
+
function ensureStateDir() {
|
|
10999
|
+
if (!existsSync3(STATE_DIR)) {
|
|
11000
|
+
mkdirSync3(STATE_DIR, { recursive: true, mode: 448 });
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
function stateFilePath(machineId) {
|
|
11004
|
+
return join4(STATE_DIR, `${machineId}.json`);
|
|
11005
|
+
}
|
|
11006
|
+
function loadDaemonState(machineId) {
|
|
11007
|
+
const filePath = stateFilePath(machineId);
|
|
11008
|
+
if (!existsSync3(filePath)) {
|
|
11009
|
+
return null;
|
|
11010
|
+
}
|
|
11011
|
+
try {
|
|
11012
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
11013
|
+
return JSON.parse(content);
|
|
11014
|
+
} catch {
|
|
11015
|
+
return null;
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
function saveDaemonState(state) {
|
|
11019
|
+
ensureStateDir();
|
|
11020
|
+
const filePath = stateFilePath(state.machineId);
|
|
11021
|
+
const tempPath = `${filePath}.tmp`;
|
|
11022
|
+
const content = JSON.stringify(state, null, 2);
|
|
11023
|
+
writeFileSync3(tempPath, content, { encoding: "utf-8", mode: 384 });
|
|
11024
|
+
renameSync2(tempPath, filePath);
|
|
11025
|
+
}
|
|
11026
|
+
function loadOrCreate(machineId) {
|
|
11027
|
+
return loadDaemonState(machineId) ?? {
|
|
11028
|
+
version: STATE_VERSION,
|
|
11029
|
+
machineId,
|
|
11030
|
+
updatedAt: new Date().toISOString(),
|
|
11031
|
+
agents: {}
|
|
11032
|
+
};
|
|
11033
|
+
}
|
|
11034
|
+
function persistAgentPid(machineId, chatroomId, role, pid, harness) {
|
|
11035
|
+
const state = loadOrCreate(machineId);
|
|
11036
|
+
state.agents[agentKey(chatroomId, role)] = {
|
|
11037
|
+
pid,
|
|
11038
|
+
harness,
|
|
11039
|
+
startedAt: new Date().toISOString()
|
|
11040
|
+
};
|
|
11041
|
+
state.updatedAt = new Date().toISOString();
|
|
11042
|
+
saveDaemonState(state);
|
|
11043
|
+
}
|
|
11044
|
+
function clearAgentPid(machineId, chatroomId, role) {
|
|
11045
|
+
const state = loadDaemonState(machineId);
|
|
11046
|
+
if (!state)
|
|
11047
|
+
return;
|
|
11048
|
+
const key = agentKey(chatroomId, role);
|
|
11049
|
+
if (!(key in state.agents))
|
|
11050
|
+
return;
|
|
11051
|
+
delete state.agents[key];
|
|
11052
|
+
state.updatedAt = new Date().toISOString();
|
|
11053
|
+
saveDaemonState(state);
|
|
11054
|
+
}
|
|
11055
|
+
function listAgentEntries(machineId) {
|
|
11056
|
+
const state = loadDaemonState(machineId);
|
|
11057
|
+
if (!state)
|
|
11058
|
+
return [];
|
|
11059
|
+
const results = [];
|
|
11060
|
+
for (const [key, entry] of Object.entries(state.agents)) {
|
|
11061
|
+
const separatorIndex = key.lastIndexOf("/");
|
|
11062
|
+
if (separatorIndex === -1)
|
|
11063
|
+
continue;
|
|
11064
|
+
const chatroomId = key.substring(0, separatorIndex);
|
|
11065
|
+
const role = key.substring(separatorIndex + 1);
|
|
11066
|
+
results.push({ chatroomId, role, entry });
|
|
11067
|
+
}
|
|
11068
|
+
return results;
|
|
11069
|
+
}
|
|
11070
|
+
function persistEventCursor(machineId, lastSeenEventId) {
|
|
11071
|
+
try {
|
|
11072
|
+
const state = loadOrCreate(machineId);
|
|
11073
|
+
state.lastSeenEventId = lastSeenEventId;
|
|
11074
|
+
state.updatedAt = new Date().toISOString();
|
|
11075
|
+
saveDaemonState(state);
|
|
11076
|
+
} catch (err) {
|
|
11077
|
+
console.warn(`⚠️ Failed to persist event cursor: ${err.message}`);
|
|
11078
|
+
}
|
|
11079
|
+
}
|
|
11080
|
+
function loadEventCursor(machineId) {
|
|
11081
|
+
const state = loadDaemonState(machineId);
|
|
11082
|
+
return state?.lastSeenEventId ?? null;
|
|
11083
|
+
}
|
|
11084
|
+
var CHATROOM_DIR3, STATE_DIR, STATE_VERSION = "1";
|
|
11085
|
+
var init_daemon_state = __esm(() => {
|
|
11086
|
+
CHATROOM_DIR3 = join4(homedir3(), ".chatroom");
|
|
11087
|
+
STATE_DIR = join4(CHATROOM_DIR3, "machines", "state");
|
|
10893
11088
|
});
|
|
10894
|
-
|
|
10895
|
-
|
|
11089
|
+
|
|
11090
|
+
// src/infrastructure/machine/intentional-stops.ts
|
|
11091
|
+
function agentKey2(chatroomId, role) {
|
|
11092
|
+
return `${chatroomId}:${role.toLowerCase()}`;
|
|
11093
|
+
}
|
|
11094
|
+
function markIntentionalStop(chatroomId, role, reason = "user.stop") {
|
|
11095
|
+
pendingStops.set(agentKey2(chatroomId, role), reason);
|
|
11096
|
+
}
|
|
11097
|
+
function consumeIntentionalStop(chatroomId, role) {
|
|
11098
|
+
const key = agentKey2(chatroomId, role);
|
|
11099
|
+
const reason = pendingStops.get(key) ?? null;
|
|
11100
|
+
if (reason !== null) {
|
|
11101
|
+
pendingStops.delete(key);
|
|
11102
|
+
}
|
|
11103
|
+
return reason;
|
|
11104
|
+
}
|
|
11105
|
+
function clearIntentionalStop(chatroomId, role) {
|
|
11106
|
+
pendingStops.delete(agentKey2(chatroomId, role));
|
|
11107
|
+
}
|
|
11108
|
+
var pendingStops;
|
|
11109
|
+
var init_intentional_stops = __esm(() => {
|
|
11110
|
+
pendingStops = new Map;
|
|
11111
|
+
});
|
|
11112
|
+
|
|
11113
|
+
// src/infrastructure/machine/index.ts
|
|
11114
|
+
var init_machine = __esm(() => {
|
|
11115
|
+
init_storage2();
|
|
11116
|
+
init_daemon_state();
|
|
11117
|
+
init_intentional_stops();
|
|
10896
11118
|
});
|
|
10897
11119
|
|
|
10898
11120
|
// src/commands/auth-status/index.ts
|
|
@@ -10900,29 +11122,10 @@ var exports_auth_status = {};
|
|
|
10900
11122
|
__export(exports_auth_status, {
|
|
10901
11123
|
authStatus: () => authStatus
|
|
10902
11124
|
});
|
|
10903
|
-
async function listAvailableModelsDefault() {
|
|
10904
|
-
const results = {};
|
|
10905
|
-
try {
|
|
10906
|
-
const { OpenCodeAgentService: OpenCodeAgentService2 } = await Promise.resolve().then(() => (init_opencode(), exports_opencode));
|
|
10907
|
-
const agentService = new OpenCodeAgentService2;
|
|
10908
|
-
if (agentService.isInstalled()) {
|
|
10909
|
-
results["opencode"] = await agentService.listModels();
|
|
10910
|
-
}
|
|
10911
|
-
} catch {}
|
|
10912
|
-
try {
|
|
10913
|
-
const { PiAgentService: PiAgentService2 } = await Promise.resolve().then(() => (init_pi(), exports_pi));
|
|
10914
|
-
const piService = new PiAgentService2;
|
|
10915
|
-
if (piService.isInstalled()) {
|
|
10916
|
-
results["pi"] = await piService.listModels();
|
|
10917
|
-
}
|
|
10918
|
-
} catch {}
|
|
10919
|
-
return results;
|
|
10920
|
-
}
|
|
10921
11125
|
async function createDefaultDeps3() {
|
|
10922
11126
|
const client2 = await getConvexClient();
|
|
10923
11127
|
return {
|
|
10924
11128
|
backend: {
|
|
10925
|
-
mutation: (endpoint, args) => client2.mutation(endpoint, args),
|
|
10926
11129
|
query: (endpoint, args) => client2.query(endpoint, args)
|
|
10927
11130
|
},
|
|
10928
11131
|
session: {
|
|
@@ -10931,8 +11134,7 @@ async function createDefaultDeps3() {
|
|
|
10931
11134
|
isAuthenticated
|
|
10932
11135
|
},
|
|
10933
11136
|
getVersion,
|
|
10934
|
-
|
|
10935
|
-
listAvailableModels: listAvailableModelsDefault
|
|
11137
|
+
loadMachineConfig
|
|
10936
11138
|
};
|
|
10937
11139
|
}
|
|
10938
11140
|
async function authStatus(deps) {
|
|
@@ -10968,32 +11170,18 @@ ${"═".repeat(50)}`);
|
|
|
10968
11170
|
if (validation.userName) {
|
|
10969
11171
|
console.log(`\uD83D\uDC64 User: ${validation.userName}`);
|
|
10970
11172
|
}
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
const availableModels = await d.listAvailableModels();
|
|
10974
|
-
await d.backend.mutation(api.machines.register, {
|
|
10975
|
-
sessionId: authData.sessionId,
|
|
10976
|
-
machineId: machineInfo.machineId,
|
|
10977
|
-
hostname: machineInfo.hostname,
|
|
10978
|
-
os: machineInfo.os,
|
|
10979
|
-
availableHarnesses: machineInfo.availableHarnesses,
|
|
10980
|
-
harnessVersions: machineInfo.harnessVersions,
|
|
10981
|
-
availableModels
|
|
10982
|
-
});
|
|
11173
|
+
const machineConfig = d.loadMachineConfig();
|
|
11174
|
+
if (machineConfig) {
|
|
10983
11175
|
console.log(`
|
|
10984
|
-
\uD83D\uDDA5️ Machine
|
|
10985
|
-
console.log(` ID: ${
|
|
10986
|
-
if (
|
|
10987
|
-
console.log(` Harnesses: ${
|
|
11176
|
+
\uD83D\uDDA5️ Machine: ${machineConfig.hostname}`);
|
|
11177
|
+
console.log(` ID: ${machineConfig.machineId}`);
|
|
11178
|
+
if (machineConfig.availableHarnesses.length > 0) {
|
|
11179
|
+
console.log(` Harnesses: ${machineConfig.availableHarnesses.join(", ")}`);
|
|
10988
11180
|
}
|
|
10989
|
-
|
|
10990
|
-
if (totalModels > 0) {
|
|
10991
|
-
console.log(` Models: ${totalModels} discovered`);
|
|
10992
|
-
}
|
|
10993
|
-
} catch (machineError) {
|
|
10994
|
-
const err = machineError;
|
|
11181
|
+
} else {
|
|
10995
11182
|
console.log(`
|
|
10996
|
-
|
|
11183
|
+
\uD83D\uDDA5️ Machine: not registered`);
|
|
11184
|
+
console.log(` Run \`chatroom machine start\` to register this machine.`);
|
|
10997
11185
|
}
|
|
10998
11186
|
} else {
|
|
10999
11187
|
console.log(`
|
|
@@ -11315,46 +11503,24 @@ async function registerAgent(chatroomId, options, deps) {
|
|
|
11315
11503
|
process.exit(1);
|
|
11316
11504
|
}
|
|
11317
11505
|
if (type === "remote") {
|
|
11506
|
+
const machineId = getMachineId();
|
|
11507
|
+
if (!machineId) {
|
|
11508
|
+
console.error(`❌ Machine not registered. Run \`chatroom machine start\` first.`);
|
|
11509
|
+
process.exit(1);
|
|
11510
|
+
}
|
|
11511
|
+
const config = loadMachineConfig();
|
|
11318
11512
|
try {
|
|
11319
|
-
|
|
11320
|
-
const availableModels = {};
|
|
11321
|
-
try {
|
|
11322
|
-
const opencodeService = new OpenCodeAgentService;
|
|
11323
|
-
if (opencodeService.isInstalled()) {
|
|
11324
|
-
availableModels["opencode"] = await opencodeService.listModels();
|
|
11325
|
-
}
|
|
11326
|
-
} catch {}
|
|
11327
|
-
try {
|
|
11328
|
-
const piService = new PiAgentService;
|
|
11329
|
-
if (piService.isInstalled()) {
|
|
11330
|
-
availableModels["pi"] = await piService.listModels();
|
|
11331
|
-
}
|
|
11332
|
-
} catch {}
|
|
11333
|
-
await d.backend.mutation(api.machines.register, {
|
|
11513
|
+
await d.backend.mutation(api.machines.recordAgentRegistered, {
|
|
11334
11514
|
sessionId,
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
harnessVersions: machineInfo.harnessVersions,
|
|
11340
|
-
availableModels
|
|
11515
|
+
chatroomId,
|
|
11516
|
+
role,
|
|
11517
|
+
agentType: "remote",
|
|
11518
|
+
machineId
|
|
11341
11519
|
});
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
role,
|
|
11347
|
-
agentType: "remote",
|
|
11348
|
-
machineId: machineInfo.machineId
|
|
11349
|
-
});
|
|
11350
|
-
} catch {}
|
|
11351
|
-
console.log(`✅ Registered as remote agent for role "${role}"`);
|
|
11352
|
-
console.log(` Machine: ${machineInfo.hostname} (${machineInfo.machineId})`);
|
|
11353
|
-
console.log(` Working directory: ${process.cwd()}`);
|
|
11354
|
-
} catch (error) {
|
|
11355
|
-
console.error(`❌ Registration failed: ${error.message}`);
|
|
11356
|
-
process.exit(1);
|
|
11357
|
-
}
|
|
11520
|
+
} catch {}
|
|
11521
|
+
console.log(`✅ Registered as remote agent for role "${role}"`);
|
|
11522
|
+
console.log(` Machine: ${config?.hostname ?? "unknown"} (${machineId})`);
|
|
11523
|
+
console.log(` Working directory: ${process.cwd()}`);
|
|
11358
11524
|
} else {
|
|
11359
11525
|
try {
|
|
11360
11526
|
await d.backend.mutation(api.machines.saveTeamAgentConfig, {
|
|
@@ -11375,8 +11541,6 @@ var init_register_agent = __esm(() => {
|
|
|
11375
11541
|
init_storage();
|
|
11376
11542
|
init_client2();
|
|
11377
11543
|
init_machine();
|
|
11378
|
-
init_opencode();
|
|
11379
|
-
init_pi();
|
|
11380
11544
|
});
|
|
11381
11545
|
// ../../services/backend/prompts/cli/task-started/command.ts
|
|
11382
11546
|
function taskStartedCommand(params) {
|
|
@@ -11737,7 +11901,6 @@ var init_session = __esm(() => {
|
|
|
11737
11901
|
var exports_get_next_task = {};
|
|
11738
11902
|
__export(exports_get_next_task, {
|
|
11739
11903
|
getNextTask: () => getNextTask,
|
|
11740
|
-
WaitForTaskSession: () => GetNextTaskSession,
|
|
11741
11904
|
GetNextTaskSession: () => GetNextTaskSession
|
|
11742
11905
|
});
|
|
11743
11906
|
async function getNextTask(chatroomId, options) {
|
|
@@ -11789,35 +11952,6 @@ async function getNextTask(chatroomId, options) {
|
|
|
11789
11952
|
console.error(`❌ Chatroom ${chatroomId} not found or access denied`);
|
|
11790
11953
|
process.exit(1);
|
|
11791
11954
|
}
|
|
11792
|
-
try {
|
|
11793
|
-
const machineInfo = ensureMachineRegistered();
|
|
11794
|
-
const availableModels = {};
|
|
11795
|
-
try {
|
|
11796
|
-
const opencodeService = new OpenCodeAgentService;
|
|
11797
|
-
if (opencodeService.isInstalled()) {
|
|
11798
|
-
availableModels["opencode"] = await opencodeService.listModels();
|
|
11799
|
-
}
|
|
11800
|
-
} catch {}
|
|
11801
|
-
try {
|
|
11802
|
-
const piService = new PiAgentService;
|
|
11803
|
-
if (piService.isInstalled()) {
|
|
11804
|
-
availableModels["pi"] = await piService.listModels();
|
|
11805
|
-
}
|
|
11806
|
-
} catch {}
|
|
11807
|
-
await client2.mutation(api.machines.register, {
|
|
11808
|
-
sessionId,
|
|
11809
|
-
machineId: machineInfo.machineId,
|
|
11810
|
-
hostname: machineInfo.hostname,
|
|
11811
|
-
os: machineInfo.os,
|
|
11812
|
-
availableHarnesses: machineInfo.availableHarnesses,
|
|
11813
|
-
harnessVersions: machineInfo.harnessVersions,
|
|
11814
|
-
availableModels
|
|
11815
|
-
});
|
|
11816
|
-
} catch (machineError) {
|
|
11817
|
-
if (!silent) {
|
|
11818
|
-
console.warn(`⚠️ Machine registration failed: ${sanitizeUnknownForTerminal(machineError.message)}`);
|
|
11819
|
-
}
|
|
11820
|
-
}
|
|
11821
11955
|
const connectionId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
11822
11956
|
let participantAgentType;
|
|
11823
11957
|
try {
|
|
@@ -11850,6 +11984,8 @@ async function getNextTask(chatroomId, options) {
|
|
|
11850
11984
|
if (initPromptResult?.prompt) {
|
|
11851
11985
|
const connectedTime = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
11852
11986
|
console.log(`[${connectedTime}] ✅ Connected. Waiting for task...
|
|
11987
|
+
`);
|
|
11988
|
+
console.log(`⚠️ IMPORTANT: This command must stay in the FOREGROUND. If moved to background, terminate and restart it — background processes cannot deliver tasks.
|
|
11853
11989
|
`);
|
|
11854
11990
|
if (!initPromptResult.hasSystemPromptControl) {
|
|
11855
11991
|
console.log("<!-- REFERENCE: Agent Initialization");
|
|
@@ -11886,14 +12022,10 @@ var init_get_next_task = __esm(() => {
|
|
|
11886
12022
|
init_env();
|
|
11887
12023
|
init_session();
|
|
11888
12024
|
init_api3();
|
|
11889
|
-
init_storage();
|
|
11890
|
-
init_client2();
|
|
11891
|
-
init_machine();
|
|
11892
|
-
init_opencode();
|
|
11893
|
-
init_pi();
|
|
12025
|
+
init_storage();
|
|
12026
|
+
init_client2();
|
|
11894
12027
|
init_error_formatting();
|
|
11895
12028
|
init_session();
|
|
11896
|
-
init_session();
|
|
11897
12029
|
});
|
|
11898
12030
|
|
|
11899
12031
|
// src/commands/task-started/index.ts
|
|
@@ -12309,7 +12441,15 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
12309
12441
|
const cliEnvPrefix2 = getCliEnvPrefix(convexUrl2);
|
|
12310
12442
|
console.error(`
|
|
12311
12443
|
❌ ERROR: ${result.error.message}`);
|
|
12312
|
-
if (result.error.
|
|
12444
|
+
if (result.error.code === "INVALID_TARGET_ROLE" && result.error.suggestedTargets) {
|
|
12445
|
+
console.error(`
|
|
12446
|
+
\uD83D\uDCCB Available handoff targets for this team:`);
|
|
12447
|
+
for (const target of result.error.suggestedTargets) {
|
|
12448
|
+
console.error(` • ${target}`);
|
|
12449
|
+
}
|
|
12450
|
+
console.error(`
|
|
12451
|
+
\uD83D\uDCA1 Check your team's workflow in the system prompt for valid handoff paths.`);
|
|
12452
|
+
} else if (result.error.suggestedTarget) {
|
|
12313
12453
|
console.error(`
|
|
12314
12454
|
\uD83D\uDCA1 Try this instead:`);
|
|
12315
12455
|
console.error("```");
|
|
@@ -13699,7 +13839,7 @@ function formatTimestamp() {
|
|
|
13699
13839
|
async function onAgentShutdown(ctx, options) {
|
|
13700
13840
|
const { chatroomId, role, pid, skipKill } = options;
|
|
13701
13841
|
try {
|
|
13702
|
-
ctx.deps.stops.mark(chatroomId, role, options.stopReason ?? "
|
|
13842
|
+
ctx.deps.stops.mark(chatroomId, role, options.stopReason ?? "user.stop");
|
|
13703
13843
|
} catch (e) {
|
|
13704
13844
|
console.log(` ⚠️ Failed to mark intentional stop for ${role}: ${e.message}`);
|
|
13705
13845
|
}
|
|
@@ -13801,187 +13941,6 @@ var init_on_daemon_shutdown = __esm(() => {
|
|
|
13801
13941
|
init_api3();
|
|
13802
13942
|
});
|
|
13803
13943
|
|
|
13804
|
-
// src/infrastructure/machine/stop-reason.ts
|
|
13805
|
-
function resolveStopReason(code2, signal, wasIntentional) {
|
|
13806
|
-
if (wasIntentional)
|
|
13807
|
-
return "intentional_stop";
|
|
13808
|
-
if (signal !== null)
|
|
13809
|
-
return "process_terminated_with_signal";
|
|
13810
|
-
if (code2 === 0)
|
|
13811
|
-
return "process_exited_with_success";
|
|
13812
|
-
return "process_terminated_unexpectedly";
|
|
13813
|
-
}
|
|
13814
|
-
|
|
13815
|
-
// src/commands/machine/daemon-start/handlers/start-agent.ts
|
|
13816
|
-
async function executeStartAgent(ctx, args) {
|
|
13817
|
-
const { chatroomId, role, agentHarness, model, workingDir, reason } = args;
|
|
13818
|
-
console.log(` ↪ start-agent command received`);
|
|
13819
|
-
console.log(` Chatroom: ${chatroomId}`);
|
|
13820
|
-
console.log(` Role: ${role}`);
|
|
13821
|
-
console.log(` Harness: ${agentHarness}`);
|
|
13822
|
-
if (reason) {
|
|
13823
|
-
console.log(` Reason: ${reason}`);
|
|
13824
|
-
}
|
|
13825
|
-
if (model) {
|
|
13826
|
-
console.log(` Model: ${model}`);
|
|
13827
|
-
}
|
|
13828
|
-
if (!workingDir) {
|
|
13829
|
-
const msg2 = `No workingDir provided in command payload for ${chatroomId}/${role}`;
|
|
13830
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13831
|
-
return { result: msg2, failed: true };
|
|
13832
|
-
}
|
|
13833
|
-
console.log(` Working dir: ${workingDir}`);
|
|
13834
|
-
try {
|
|
13835
|
-
const dirStat = await ctx.deps.fs.stat(workingDir);
|
|
13836
|
-
if (!dirStat.isDirectory()) {
|
|
13837
|
-
const msg2 = `Working directory is not a directory: ${workingDir}`;
|
|
13838
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13839
|
-
return { result: msg2, failed: true };
|
|
13840
|
-
}
|
|
13841
|
-
} catch {
|
|
13842
|
-
const msg2 = `Working directory does not exist: ${workingDir}`;
|
|
13843
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13844
|
-
return { result: msg2, failed: true };
|
|
13845
|
-
}
|
|
13846
|
-
try {
|
|
13847
|
-
const existingConfigs = await ctx.deps.backend.query(api.machines.getAgentConfigs, {
|
|
13848
|
-
sessionId: ctx.sessionId,
|
|
13849
|
-
chatroomId
|
|
13850
|
-
});
|
|
13851
|
-
const existingConfig = existingConfigs.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
|
|
13852
|
-
const backendPid = existingConfig?.spawnedAgentPid;
|
|
13853
|
-
const localEntry = ctx.deps.machine.listAgentEntries(ctx.machineId).find((e) => e.chatroomId === chatroomId && e.role.toLowerCase() === role.toLowerCase());
|
|
13854
|
-
const localPid = localEntry?.entry.pid;
|
|
13855
|
-
const pidsToKill = [
|
|
13856
|
-
...new Set([backendPid, localPid].filter((p) => p !== undefined))
|
|
13857
|
-
];
|
|
13858
|
-
const anyService = ctx.agentServices.values().next().value;
|
|
13859
|
-
for (const pid2 of pidsToKill) {
|
|
13860
|
-
const isAlive = anyService ? anyService.isAlive(pid2) : false;
|
|
13861
|
-
if (isAlive) {
|
|
13862
|
-
console.log(` ⚠️ Existing agent detected (PID: ${pid2}) — stopping before respawn`);
|
|
13863
|
-
await onAgentShutdown(ctx, { chatroomId, role, pid: pid2, stopReason: "daemon_respawn_stop" });
|
|
13864
|
-
console.log(` ✅ Existing agent stopped (PID: ${pid2})`);
|
|
13865
|
-
}
|
|
13866
|
-
}
|
|
13867
|
-
} catch (e) {
|
|
13868
|
-
console.log(` ⚠️ Could not check for existing agent (proceeding): ${e.message}`);
|
|
13869
|
-
}
|
|
13870
|
-
const convexUrl = getConvexUrl();
|
|
13871
|
-
const initPromptResult = await ctx.deps.backend.query(api.messages.getInitPrompt, {
|
|
13872
|
-
sessionId: ctx.sessionId,
|
|
13873
|
-
chatroomId,
|
|
13874
|
-
role,
|
|
13875
|
-
convexUrl
|
|
13876
|
-
});
|
|
13877
|
-
if (!initPromptResult?.prompt) {
|
|
13878
|
-
const msg2 = "Failed to fetch init prompt from backend";
|
|
13879
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13880
|
-
return { result: msg2, failed: true };
|
|
13881
|
-
}
|
|
13882
|
-
console.log(` Fetched split init prompt from backend`);
|
|
13883
|
-
const service = ctx.agentServices.get(agentHarness);
|
|
13884
|
-
if (!service) {
|
|
13885
|
-
const msg2 = `Unknown agent harness: ${agentHarness}`;
|
|
13886
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13887
|
-
return { result: msg2, failed: true };
|
|
13888
|
-
}
|
|
13889
|
-
let spawnResult;
|
|
13890
|
-
try {
|
|
13891
|
-
spawnResult = await service.spawn({
|
|
13892
|
-
workingDir,
|
|
13893
|
-
prompt: initPromptResult.initialMessage,
|
|
13894
|
-
systemPrompt: initPromptResult.rolePrompt,
|
|
13895
|
-
model,
|
|
13896
|
-
context: { machineId: ctx.machineId, chatroomId, role }
|
|
13897
|
-
});
|
|
13898
|
-
} catch (e) {
|
|
13899
|
-
const msg2 = `Failed to spawn agent: ${e.message}`;
|
|
13900
|
-
console.log(` ⚠️ ${msg2}`);
|
|
13901
|
-
return { result: msg2, failed: true };
|
|
13902
|
-
}
|
|
13903
|
-
const { pid } = spawnResult;
|
|
13904
|
-
const msg = `Agent spawned (PID: ${pid})`;
|
|
13905
|
-
console.log(` ✅ ${msg}`);
|
|
13906
|
-
try {
|
|
13907
|
-
await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
|
|
13908
|
-
sessionId: ctx.sessionId,
|
|
13909
|
-
machineId: ctx.machineId,
|
|
13910
|
-
chatroomId,
|
|
13911
|
-
role,
|
|
13912
|
-
pid,
|
|
13913
|
-
model
|
|
13914
|
-
});
|
|
13915
|
-
console.log(` Updated backend with PID: ${pid}`);
|
|
13916
|
-
ctx.deps.machine.persistAgentPid(ctx.machineId, chatroomId, role, pid, agentHarness);
|
|
13917
|
-
} catch (e) {
|
|
13918
|
-
console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
|
|
13919
|
-
}
|
|
13920
|
-
ctx.events.emit("agent:started", {
|
|
13921
|
-
chatroomId,
|
|
13922
|
-
role,
|
|
13923
|
-
pid,
|
|
13924
|
-
harness: agentHarness,
|
|
13925
|
-
model
|
|
13926
|
-
});
|
|
13927
|
-
spawnResult.onExit(({ code: code2, signal }) => {
|
|
13928
|
-
const pendingReason = ctx.deps.stops.consume(chatroomId, role);
|
|
13929
|
-
const stopReason = pendingReason ?? resolveStopReason(code2, signal, false);
|
|
13930
|
-
ctx.events.emit("agent:exited", {
|
|
13931
|
-
chatroomId,
|
|
13932
|
-
role,
|
|
13933
|
-
pid,
|
|
13934
|
-
code: code2,
|
|
13935
|
-
signal,
|
|
13936
|
-
stopReason,
|
|
13937
|
-
intentional: pendingReason !== null
|
|
13938
|
-
});
|
|
13939
|
-
});
|
|
13940
|
-
if (spawnResult.onAgentEnd) {
|
|
13941
|
-
spawnResult.onAgentEnd(() => {
|
|
13942
|
-
try {
|
|
13943
|
-
ctx.deps.processes.kill(-pid, "SIGTERM");
|
|
13944
|
-
} catch {}
|
|
13945
|
-
});
|
|
13946
|
-
}
|
|
13947
|
-
let lastReportedTokenAt = 0;
|
|
13948
|
-
spawnResult.onOutput(() => {
|
|
13949
|
-
const now = Date.now();
|
|
13950
|
-
if (now - lastReportedTokenAt >= 30000) {
|
|
13951
|
-
lastReportedTokenAt = now;
|
|
13952
|
-
ctx.deps.backend.mutation(api.participants.updateTokenActivity, {
|
|
13953
|
-
sessionId: ctx.sessionId,
|
|
13954
|
-
chatroomId,
|
|
13955
|
-
role
|
|
13956
|
-
}).catch(() => {});
|
|
13957
|
-
}
|
|
13958
|
-
});
|
|
13959
|
-
return { result: msg, failed: false };
|
|
13960
|
-
}
|
|
13961
|
-
var init_start_agent = __esm(() => {
|
|
13962
|
-
init_api3();
|
|
13963
|
-
init_client2();
|
|
13964
|
-
});
|
|
13965
|
-
|
|
13966
|
-
// src/events/daemon/agent/on-request-start-agent.ts
|
|
13967
|
-
async function onRequestStartAgent(ctx, event) {
|
|
13968
|
-
if (Date.now() > event.deadline) {
|
|
13969
|
-
console.log(`[daemon] ⏰ Skipping expired agent.requestStart for role=${event.role} (deadline passed)`);
|
|
13970
|
-
return;
|
|
13971
|
-
}
|
|
13972
|
-
await executeStartAgent(ctx, {
|
|
13973
|
-
chatroomId: event.chatroomId,
|
|
13974
|
-
role: event.role,
|
|
13975
|
-
agentHarness: event.agentHarness,
|
|
13976
|
-
model: event.model,
|
|
13977
|
-
workingDir: event.workingDir,
|
|
13978
|
-
reason: event.reason
|
|
13979
|
-
});
|
|
13980
|
-
}
|
|
13981
|
-
var init_on_request_start_agent = __esm(() => {
|
|
13982
|
-
init_start_agent();
|
|
13983
|
-
});
|
|
13984
|
-
|
|
13985
13944
|
// src/commands/machine/daemon-start/handlers/shared.ts
|
|
13986
13945
|
async function clearAgentPidEverywhere(ctx, chatroomId, role) {
|
|
13987
13946
|
try {
|
|
@@ -13989,107 +13948,44 @@ async function clearAgentPidEverywhere(ctx, chatroomId, role) {
|
|
|
13989
13948
|
sessionId: ctx.sessionId,
|
|
13990
13949
|
machineId: ctx.machineId,
|
|
13991
13950
|
chatroomId,
|
|
13992
|
-
role,
|
|
13993
|
-
pid: undefined
|
|
13994
|
-
});
|
|
13995
|
-
} catch (e) {
|
|
13996
|
-
console.log(` ⚠️ Failed to clear PID in backend: ${e.message}`);
|
|
13997
|
-
}
|
|
13998
|
-
ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
|
|
13999
|
-
}
|
|
14000
|
-
var init_shared = __esm(() => {
|
|
14001
|
-
init_api3();
|
|
14002
|
-
});
|
|
14003
|
-
|
|
14004
|
-
// src/commands/machine/daemon-start/handlers/stop-agent.ts
|
|
14005
|
-
async function executeStopAgent(ctx, args) {
|
|
14006
|
-
const { chatroomId, role } = args;
|
|
14007
|
-
console.log(` ↪ stop-agent command received`);
|
|
14008
|
-
console.log(` Chatroom: ${chatroomId}`);
|
|
14009
|
-
console.log(` Role: ${role}`);
|
|
14010
|
-
const configsResult = await ctx.deps.backend.query(api.machines.getAgentConfigs, {
|
|
14011
|
-
sessionId: ctx.sessionId,
|
|
14012
|
-
chatroomId
|
|
14013
|
-
});
|
|
14014
|
-
const targetConfig = configsResult.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
|
|
14015
|
-
const backendPid = targetConfig?.spawnedAgentPid;
|
|
14016
|
-
const localEntry = ctx.deps.machine.listAgentEntries(ctx.machineId).find((e) => e.chatroomId === chatroomId && e.role.toLowerCase() === role.toLowerCase());
|
|
14017
|
-
const localPid = localEntry?.entry.pid;
|
|
14018
|
-
const allPids = [...new Set([backendPid, localPid].filter((p) => p !== undefined))];
|
|
14019
|
-
if (allPids.length === 0) {
|
|
14020
|
-
const msg = "No running agent found (no PID recorded)";
|
|
14021
|
-
console.log(` ⚠️ ${msg}`);
|
|
14022
|
-
return { result: msg, failed: true };
|
|
14023
|
-
}
|
|
14024
|
-
const anyService = ctx.agentServices.values().next().value;
|
|
14025
|
-
let anyKilled = false;
|
|
14026
|
-
let lastError = null;
|
|
14027
|
-
for (const pid of allPids) {
|
|
14028
|
-
console.log(` Stopping agent with PID: ${pid}`);
|
|
14029
|
-
const isAlive = anyService ? anyService.isAlive(pid) : false;
|
|
14030
|
-
if (!isAlive) {
|
|
14031
|
-
console.log(` ⚠️ PID ${pid} not found — process already exited or was never started`);
|
|
14032
|
-
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
14033
|
-
console.log(` Cleared stale PID`);
|
|
14034
|
-
try {
|
|
14035
|
-
await ctx.deps.backend.mutation(api.participants.leave, {
|
|
14036
|
-
sessionId: ctx.sessionId,
|
|
14037
|
-
chatroomId,
|
|
14038
|
-
role
|
|
14039
|
-
});
|
|
14040
|
-
console.log(` Removed participant record`);
|
|
14041
|
-
} catch {}
|
|
14042
|
-
continue;
|
|
14043
|
-
}
|
|
14044
|
-
try {
|
|
14045
|
-
const shutdownResult = await onAgentShutdown(ctx, {
|
|
14046
|
-
chatroomId,
|
|
14047
|
-
role,
|
|
14048
|
-
pid
|
|
14049
|
-
});
|
|
14050
|
-
const msg = shutdownResult.killed ? `Agent stopped (PID: ${pid})` : `Agent stop attempted (PID: ${pid}) — process may still be running`;
|
|
14051
|
-
console.log(` ${shutdownResult.killed ? "✅" : "⚠️ "} ${msg}`);
|
|
14052
|
-
if (shutdownResult.killed) {
|
|
14053
|
-
anyKilled = true;
|
|
14054
|
-
}
|
|
14055
|
-
} catch (e) {
|
|
14056
|
-
lastError = e;
|
|
14057
|
-
console.log(` ⚠️ Failed to stop agent (PID: ${pid}): ${e.message}`);
|
|
14058
|
-
}
|
|
14059
|
-
}
|
|
14060
|
-
if (lastError && !anyKilled) {
|
|
14061
|
-
const msg = `Failed to stop agent: ${lastError.message}`;
|
|
14062
|
-
console.log(` ⚠️ ${msg}`);
|
|
14063
|
-
return { result: msg, failed: true };
|
|
14064
|
-
}
|
|
14065
|
-
if (!anyKilled) {
|
|
14066
|
-
return {
|
|
14067
|
-
result: `All recorded PIDs appear stale (processes not found or belong to different programs)`,
|
|
14068
|
-
failed: true
|
|
14069
|
-
};
|
|
13951
|
+
role,
|
|
13952
|
+
pid: undefined
|
|
13953
|
+
});
|
|
13954
|
+
} catch (e) {
|
|
13955
|
+
console.log(` ⚠️ Failed to clear PID in backend: ${e.message}`);
|
|
14070
13956
|
}
|
|
14071
|
-
|
|
14072
|
-
return { result: `Agent stopped${killedCount}`, failed: false };
|
|
13957
|
+
ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
|
|
14073
13958
|
}
|
|
14074
|
-
var
|
|
13959
|
+
var init_shared = __esm(() => {
|
|
14075
13960
|
init_api3();
|
|
14076
|
-
init_shared();
|
|
14077
13961
|
});
|
|
14078
13962
|
|
|
14079
|
-
// src/
|
|
14080
|
-
async function
|
|
14081
|
-
|
|
14082
|
-
|
|
13963
|
+
// src/commands/machine/daemon-start/handlers/state-recovery.ts
|
|
13964
|
+
async function recoverAgentState(ctx) {
|
|
13965
|
+
const entries = ctx.deps.machine.listAgentEntries(ctx.machineId);
|
|
13966
|
+
if (entries.length === 0) {
|
|
13967
|
+
console.log(` No agent entries found — nothing to recover`);
|
|
14083
13968
|
return;
|
|
14084
13969
|
}
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
13970
|
+
let recovered = 0;
|
|
13971
|
+
let cleared = 0;
|
|
13972
|
+
for (const { chatroomId, role, entry } of entries) {
|
|
13973
|
+
const { pid, harness } = entry;
|
|
13974
|
+
const service = ctx.agentServices.get(harness) ?? ctx.agentServices.values().next().value;
|
|
13975
|
+
const alive = service ? service.isAlive(pid) : false;
|
|
13976
|
+
if (alive) {
|
|
13977
|
+
console.log(` ✅ Recovered: ${role} (PID ${pid}, harness: ${harness})`);
|
|
13978
|
+
recovered++;
|
|
13979
|
+
} else {
|
|
13980
|
+
console.log(` \uD83E\uDDF9 Stale PID ${pid} for ${role} — clearing`);
|
|
13981
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
13982
|
+
cleared++;
|
|
13983
|
+
}
|
|
13984
|
+
}
|
|
13985
|
+
console.log(` Recovery complete: ${recovered} alive, ${cleared} stale cleared`);
|
|
14090
13986
|
}
|
|
14091
|
-
var
|
|
14092
|
-
|
|
13987
|
+
var init_state_recovery = __esm(() => {
|
|
13988
|
+
init_shared();
|
|
14093
13989
|
});
|
|
14094
13990
|
|
|
14095
13991
|
// src/commands/machine/pid.ts
|
|
@@ -14178,459 +14074,693 @@ var init_pid = __esm(() => {
|
|
|
14178
14074
|
CHATROOM_DIR4 = join5(homedir4(), ".chatroom");
|
|
14179
14075
|
});
|
|
14180
14076
|
|
|
14181
|
-
// src/
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14077
|
+
// src/events/daemon/event-bus.ts
|
|
14078
|
+
class DaemonEventBus {
|
|
14079
|
+
listeners = new Map;
|
|
14080
|
+
on(event, listener) {
|
|
14081
|
+
if (!this.listeners.has(event)) {
|
|
14082
|
+
this.listeners.set(event, new Set);
|
|
14083
|
+
}
|
|
14084
|
+
this.listeners.get(event).add(listener);
|
|
14085
|
+
return () => {
|
|
14086
|
+
this.listeners.get(event)?.delete(listener);
|
|
14087
|
+
};
|
|
14088
|
+
}
|
|
14089
|
+
emit(event, payload) {
|
|
14090
|
+
const set = this.listeners.get(event);
|
|
14091
|
+
if (!set)
|
|
14092
|
+
return;
|
|
14093
|
+
for (const listener of set) {
|
|
14094
|
+
try {
|
|
14095
|
+
listener(payload);
|
|
14096
|
+
} catch (err) {
|
|
14097
|
+
console.warn(`[EventBus] Listener error on "${event}": ${err.message}`);
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
}
|
|
14101
|
+
removeAllListeners() {
|
|
14102
|
+
this.listeners.clear();
|
|
14103
|
+
}
|
|
14185
14104
|
}
|
|
14186
14105
|
|
|
14187
|
-
// src/
|
|
14188
|
-
|
|
14189
|
-
const
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
}
|
|
14106
|
+
// src/events/daemon/agent/on-agent-exited.ts
|
|
14107
|
+
function onAgentExited(ctx, payload) {
|
|
14108
|
+
const { chatroomId, role, pid, code: code2, signal, stopReason, intentional } = payload;
|
|
14109
|
+
const ts = formatTimestamp();
|
|
14110
|
+
console.log(`[${ts}] Agent stopped: ${stopReason} (${role})`);
|
|
14111
|
+
const isDaemonRespawn = stopReason === "daemon.respawn";
|
|
14112
|
+
const isIntentional = intentional && !isDaemonRespawn;
|
|
14113
|
+
if (isIntentional) {
|
|
14114
|
+
console.log(`[${ts}] ℹ️ Agent process exited after intentional stop ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14115
|
+
} else if (isDaemonRespawn) {
|
|
14116
|
+
console.log(`[${ts}] \uD83D\uDD04 Agent process stopped for respawn ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14117
|
+
} else {
|
|
14118
|
+
console.log(`[${ts}] ⚠️ Agent process exited ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14196
14119
|
}
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14120
|
+
ctx.deps.backend.mutation(api.machines.recordAgentExited, {
|
|
14121
|
+
sessionId: ctx.sessionId,
|
|
14122
|
+
machineId: ctx.machineId,
|
|
14123
|
+
chatroomId,
|
|
14124
|
+
role,
|
|
14125
|
+
pid,
|
|
14126
|
+
intentional,
|
|
14127
|
+
stopReason,
|
|
14128
|
+
stopSignal: stopReason === "agent_process.signal" ? signal ?? undefined : undefined,
|
|
14129
|
+
exitCode: code2 ?? undefined,
|
|
14130
|
+
signal: signal ?? undefined
|
|
14131
|
+
}).catch((err) => {
|
|
14132
|
+
console.log(` ⚠️ Failed to record agent exit event: ${err.message}`);
|
|
14133
|
+
});
|
|
14134
|
+
ctx.deps.machine.clearAgentPid(ctx.machineId, chatroomId, role);
|
|
14135
|
+
for (const service of ctx.agentServices.values()) {
|
|
14136
|
+
service.untrack(pid);
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
var init_on_agent_exited = __esm(() => {
|
|
14140
|
+
init_api3();
|
|
14141
|
+
});
|
|
14142
|
+
|
|
14143
|
+
// src/events/daemon/agent/on-agent-started.ts
|
|
14144
|
+
function onAgentStarted(ctx, payload) {
|
|
14145
|
+
const ts = formatTimestamp();
|
|
14146
|
+
console.log(`[${ts}] \uD83D\uDFE2 Agent started: ${payload.role} (PID: ${payload.pid}, harness: ${payload.harness})`);
|
|
14147
|
+
}
|
|
14148
|
+
var init_on_agent_started = () => {};
|
|
14149
|
+
|
|
14150
|
+
// src/events/daemon/agent/on-agent-stopped.ts
|
|
14151
|
+
function onAgentStopped(ctx, payload) {
|
|
14152
|
+
const ts = formatTimestamp();
|
|
14153
|
+
console.log(`[${ts}] \uD83D\uDD34 Agent stopped: ${payload.role} (PID: ${payload.pid})`);
|
|
14154
|
+
}
|
|
14155
|
+
var init_on_agent_stopped = () => {};
|
|
14156
|
+
|
|
14157
|
+
// src/events/daemon/register-listeners.ts
|
|
14158
|
+
function registerEventListeners(ctx) {
|
|
14159
|
+
const unsubs = [];
|
|
14160
|
+
unsubs.push(ctx.events.on("agent:exited", (payload) => onAgentExited(ctx, payload)));
|
|
14161
|
+
unsubs.push(ctx.events.on("agent:started", (payload) => onAgentStarted(ctx, payload)));
|
|
14162
|
+
unsubs.push(ctx.events.on("agent:stopped", (payload) => onAgentStopped(ctx, payload)));
|
|
14163
|
+
return () => {
|
|
14164
|
+
for (const unsub of unsubs) {
|
|
14165
|
+
unsub();
|
|
14166
|
+
}
|
|
14167
|
+
};
|
|
14168
|
+
}
|
|
14169
|
+
var init_register_listeners = __esm(() => {
|
|
14170
|
+
init_on_agent_exited();
|
|
14171
|
+
init_on_agent_started();
|
|
14172
|
+
init_on_agent_stopped();
|
|
14173
|
+
});
|
|
14174
|
+
|
|
14175
|
+
// src/commands/machine/daemon-start/init.ts
|
|
14176
|
+
import { stat } from "node:fs/promises";
|
|
14177
|
+
async function discoverModels(agentServices) {
|
|
14178
|
+
const results = {};
|
|
14179
|
+
for (const [harness, service] of agentServices) {
|
|
14180
|
+
if (service.isInstalled()) {
|
|
14181
|
+
try {
|
|
14182
|
+
results[harness] = await service.listModels();
|
|
14183
|
+
} catch {
|
|
14184
|
+
results[harness] = [];
|
|
14185
|
+
}
|
|
14186
|
+
}
|
|
14187
|
+
}
|
|
14188
|
+
return results;
|
|
14189
|
+
}
|
|
14190
|
+
function createDefaultDeps16() {
|
|
14191
|
+
return {
|
|
14192
|
+
backend: {
|
|
14193
|
+
mutation: async () => {
|
|
14194
|
+
throw new Error("Backend not initialized");
|
|
14195
|
+
},
|
|
14196
|
+
query: async () => {
|
|
14197
|
+
throw new Error("Backend not initialized");
|
|
14198
|
+
}
|
|
14199
|
+
},
|
|
14200
|
+
processes: {
|
|
14201
|
+
kill: (pid, signal) => process.kill(pid, signal)
|
|
14202
|
+
},
|
|
14203
|
+
fs: {
|
|
14204
|
+
stat
|
|
14205
|
+
},
|
|
14206
|
+
stops: {
|
|
14207
|
+
mark: markIntentionalStop,
|
|
14208
|
+
consume: consumeIntentionalStop,
|
|
14209
|
+
clear: clearIntentionalStop
|
|
14210
|
+
},
|
|
14211
|
+
machine: {
|
|
14212
|
+
clearAgentPid,
|
|
14213
|
+
persistAgentPid,
|
|
14214
|
+
listAgentEntries,
|
|
14215
|
+
persistEventCursor,
|
|
14216
|
+
loadEventCursor
|
|
14217
|
+
},
|
|
14218
|
+
clock: {
|
|
14219
|
+
now: () => Date.now(),
|
|
14220
|
+
delay: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
|
|
14221
|
+
}
|
|
14222
|
+
};
|
|
14223
|
+
}
|
|
14224
|
+
function validateAuthentication(convexUrl) {
|
|
14225
|
+
const sessionId = getSessionId();
|
|
14226
|
+
if (!sessionId) {
|
|
14227
|
+
const otherUrls = getOtherSessionUrls();
|
|
14228
|
+
console.error(`❌ Not authenticated for: ${convexUrl}`);
|
|
14229
|
+
if (otherUrls.length > 0) {
|
|
14230
|
+
console.error(`
|
|
14231
|
+
\uD83D\uDCA1 You have sessions for other environments:`);
|
|
14232
|
+
for (const url of otherUrls) {
|
|
14233
|
+
console.error(` • ${url}`);
|
|
14234
|
+
}
|
|
14235
|
+
}
|
|
14236
|
+
console.error(`
|
|
14237
|
+
Run: chatroom auth login`);
|
|
14238
|
+
releaseLock();
|
|
14239
|
+
process.exit(1);
|
|
14240
|
+
}
|
|
14241
|
+
return sessionId;
|
|
14242
|
+
}
|
|
14243
|
+
async function validateSession(client2, sessionId, convexUrl) {
|
|
14244
|
+
const validation = await client2.query(api.cliAuth.validateSession, { sessionId });
|
|
14245
|
+
if (!validation.valid) {
|
|
14246
|
+
console.error(`❌ Session invalid: ${validation.reason}`);
|
|
14247
|
+
console.error(`
|
|
14248
|
+
Run: chatroom auth login`);
|
|
14249
|
+
releaseLock();
|
|
14250
|
+
process.exit(1);
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
14253
|
+
function setupMachine() {
|
|
14254
|
+
ensureMachineRegistered();
|
|
14255
|
+
const config3 = loadMachineConfig();
|
|
14256
|
+
return config3;
|
|
14257
|
+
}
|
|
14258
|
+
async function registerCapabilities(client2, sessionId, config3, agentServices) {
|
|
14259
|
+
const { machineId } = config3;
|
|
14260
|
+
const availableModels = await discoverModels(agentServices);
|
|
14203
14261
|
try {
|
|
14204
|
-
await
|
|
14205
|
-
sessionId
|
|
14206
|
-
machineId
|
|
14207
|
-
hostname:
|
|
14208
|
-
os:
|
|
14209
|
-
availableHarnesses:
|
|
14210
|
-
harnessVersions:
|
|
14211
|
-
availableModels
|
|
14262
|
+
await client2.mutation(api.machines.register, {
|
|
14263
|
+
sessionId,
|
|
14264
|
+
machineId,
|
|
14265
|
+
hostname: config3.hostname,
|
|
14266
|
+
os: config3.os,
|
|
14267
|
+
availableHarnesses: config3.availableHarnesses,
|
|
14268
|
+
harnessVersions: config3.harnessVersions,
|
|
14269
|
+
availableModels
|
|
14270
|
+
});
|
|
14271
|
+
} catch (error) {
|
|
14272
|
+
console.warn(`⚠️ Machine registration update failed: ${error.message}`);
|
|
14273
|
+
}
|
|
14274
|
+
return availableModels;
|
|
14275
|
+
}
|
|
14276
|
+
async function connectDaemon(client2, sessionId, machineId, convexUrl) {
|
|
14277
|
+
try {
|
|
14278
|
+
await client2.mutation(api.machines.updateDaemonStatus, {
|
|
14279
|
+
sessionId,
|
|
14280
|
+
machineId,
|
|
14281
|
+
connected: true
|
|
14212
14282
|
});
|
|
14213
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDD04 Model refresh: ${totalCount > 0 ? `${totalCount} models` : "none discovered"}`);
|
|
14214
14283
|
} catch (error) {
|
|
14215
|
-
|
|
14284
|
+
if (isNetworkError(error)) {
|
|
14285
|
+
formatConnectivityError(error, convexUrl);
|
|
14286
|
+
} else {
|
|
14287
|
+
console.error(`❌ Failed to update daemon status: ${error.message}`);
|
|
14288
|
+
}
|
|
14289
|
+
releaseLock();
|
|
14290
|
+
process.exit(1);
|
|
14216
14291
|
}
|
|
14217
14292
|
}
|
|
14218
|
-
function
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
}
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
processedPingIds.delete(id);
|
|
14227
|
-
}
|
|
14293
|
+
function logStartup(ctx, availableModels) {
|
|
14294
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDE80 Daemon started`);
|
|
14295
|
+
console.log(` CLI version: ${getVersion()}`);
|
|
14296
|
+
console.log(` Machine ID: ${ctx.machineId}`);
|
|
14297
|
+
console.log(` Hostname: ${ctx.config?.hostname ?? "unknown"}`);
|
|
14298
|
+
console.log(` Available harnesses: ${ctx.config?.availableHarnesses.join(", ") || "none"}`);
|
|
14299
|
+
console.log(` Available models: ${Object.keys(availableModels).length > 0 ? `${Object.values(availableModels).flat().length} models across ${Object.keys(availableModels).join(", ")}` : "none discovered"}`);
|
|
14300
|
+
console.log(` PID: ${process.pid}`);
|
|
14228
14301
|
}
|
|
14229
|
-
async function
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
if (processedCommandIds.has(eventId))
|
|
14238
|
-
return;
|
|
14239
|
-
processedCommandIds.set(eventId, Date.now());
|
|
14240
|
-
await onRequestStopAgent(ctx, event);
|
|
14241
|
-
} else if (event.type === "daemon.ping") {
|
|
14242
|
-
if (processedPingIds.has(eventId))
|
|
14243
|
-
return;
|
|
14244
|
-
processedPingIds.set(eventId, Date.now());
|
|
14245
|
-
handlePing();
|
|
14246
|
-
await ctx.deps.backend.mutation(api.machines.ackPing, {
|
|
14247
|
-
sessionId: ctx.sessionId,
|
|
14248
|
-
machineId: ctx.machineId,
|
|
14249
|
-
pingEventId: event._id
|
|
14250
|
-
});
|
|
14302
|
+
async function recoverState(ctx) {
|
|
14303
|
+
console.log(`
|
|
14304
|
+
[${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
|
|
14305
|
+
try {
|
|
14306
|
+
await recoverAgentState(ctx);
|
|
14307
|
+
} catch (e) {
|
|
14308
|
+
console.log(` ⚠️ Recovery failed: ${e.message}`);
|
|
14309
|
+
console.log(` Continuing with fresh state`);
|
|
14251
14310
|
}
|
|
14252
14311
|
}
|
|
14253
|
-
async function
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14312
|
+
async function initDaemon() {
|
|
14313
|
+
if (!acquireLock()) {
|
|
14314
|
+
process.exit(1);
|
|
14315
|
+
}
|
|
14316
|
+
const convexUrl = getConvexUrl();
|
|
14317
|
+
const sessionId = validateAuthentication(convexUrl);
|
|
14318
|
+
const client2 = await getConvexClient();
|
|
14319
|
+
const typedSessionId = sessionId;
|
|
14320
|
+
await validateSession(client2, typedSessionId, convexUrl);
|
|
14321
|
+
const config3 = setupMachine();
|
|
14322
|
+
const { machineId } = config3;
|
|
14323
|
+
initHarnessRegistry();
|
|
14324
|
+
const agentServices = new Map(getAllHarnesses().map((s) => [s.id, s]));
|
|
14325
|
+
const availableModels = await registerCapabilities(client2, typedSessionId, config3, agentServices);
|
|
14326
|
+
await connectDaemon(client2, typedSessionId, machineId, convexUrl);
|
|
14327
|
+
const deps = createDefaultDeps16();
|
|
14328
|
+
deps.backend.mutation = (endpoint, args) => client2.mutation(endpoint, args);
|
|
14329
|
+
deps.backend.query = (endpoint, args) => client2.query(endpoint, args);
|
|
14330
|
+
const events = new DaemonEventBus;
|
|
14331
|
+
const ctx = {
|
|
14332
|
+
client: client2,
|
|
14333
|
+
sessionId: typedSessionId,
|
|
14334
|
+
machineId,
|
|
14335
|
+
config: config3,
|
|
14336
|
+
deps,
|
|
14337
|
+
events,
|
|
14338
|
+
agentServices
|
|
14274
14339
|
};
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
console.log(`
|
|
14280
|
-
Listening for commands...`);
|
|
14281
|
-
console.log(`Press Ctrl+C to stop
|
|
14282
|
-
`);
|
|
14283
|
-
const processedCommandIds = new Map;
|
|
14284
|
-
const processedPingIds = new Map;
|
|
14285
|
-
wsClient2.onUpdate(api.machines.getCommandEvents, {
|
|
14286
|
-
sessionId: ctx.sessionId,
|
|
14287
|
-
machineId: ctx.machineId
|
|
14288
|
-
}, async (result) => {
|
|
14289
|
-
if (!result.events || result.events.length === 0)
|
|
14290
|
-
return;
|
|
14291
|
-
evictStaleDedupEntries(processedCommandIds, processedPingIds);
|
|
14292
|
-
for (const event of result.events) {
|
|
14293
|
-
try {
|
|
14294
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDCE1 Stream command event: ${event.type}`);
|
|
14295
|
-
await dispatchCommandEvent(ctx, event, processedCommandIds, processedPingIds);
|
|
14296
|
-
} catch (err) {
|
|
14297
|
-
console.error(`[${formatTimestamp()}] ❌ Stream command event failed: ${err.message}`);
|
|
14298
|
-
}
|
|
14299
|
-
}
|
|
14300
|
-
});
|
|
14301
|
-
const modelRefreshTimer = setInterval(() => {
|
|
14302
|
-
refreshModels(ctx).catch((err) => {
|
|
14303
|
-
console.warn(`[${formatTimestamp()}] ⚠️ Model refresh error: ${err.message}`);
|
|
14304
|
-
});
|
|
14305
|
-
}, MODEL_REFRESH_INTERVAL_MS);
|
|
14306
|
-
modelRefreshTimer.unref();
|
|
14307
|
-
return await new Promise(() => {});
|
|
14340
|
+
registerEventListeners(ctx);
|
|
14341
|
+
logStartup(ctx, availableModels);
|
|
14342
|
+
await recoverState(ctx);
|
|
14343
|
+
return ctx;
|
|
14308
14344
|
}
|
|
14309
|
-
var
|
|
14310
|
-
|
|
14345
|
+
var init_init2 = __esm(() => {
|
|
14346
|
+
init_state_recovery();
|
|
14311
14347
|
init_api3();
|
|
14348
|
+
init_storage();
|
|
14312
14349
|
init_client2();
|
|
14313
14350
|
init_machine();
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14351
|
+
init_intentional_stops();
|
|
14352
|
+
init_remote_agents();
|
|
14353
|
+
init_error_formatting();
|
|
14354
|
+
init_version();
|
|
14317
14355
|
init_pid();
|
|
14318
|
-
|
|
14356
|
+
init_register_listeners();
|
|
14319
14357
|
});
|
|
14320
14358
|
|
|
14321
|
-
// src/
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
return;
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
for (const { chatroomId, role, entry } of entries) {
|
|
14331
|
-
const { pid, harness } = entry;
|
|
14332
|
-
const service = ctx.agentServices.get(harness) ?? ctx.agentServices.values().next().value;
|
|
14333
|
-
const alive = service ? service.isAlive(pid) : false;
|
|
14334
|
-
if (alive) {
|
|
14335
|
-
console.log(` ✅ Recovered: ${role} (PID ${pid}, harness: ${harness})`);
|
|
14336
|
-
recovered++;
|
|
14337
|
-
} else {
|
|
14338
|
-
console.log(` \uD83E\uDDF9 Stale PID ${pid} for ${role} — clearing`);
|
|
14339
|
-
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
14340
|
-
cleared++;
|
|
14341
|
-
}
|
|
14342
|
-
}
|
|
14343
|
-
console.log(` Recovery complete: ${recovered} alive, ${cleared} stale cleared`);
|
|
14359
|
+
// src/infrastructure/machine/stop-reason.ts
|
|
14360
|
+
function resolveStopReason(code2, signal, wasIntentional) {
|
|
14361
|
+
if (wasIntentional)
|
|
14362
|
+
return "user.stop";
|
|
14363
|
+
if (signal !== null)
|
|
14364
|
+
return "agent_process.signal";
|
|
14365
|
+
if (code2 === 0)
|
|
14366
|
+
return "agent_process.exited_clean";
|
|
14367
|
+
return "agent_process.crashed";
|
|
14344
14368
|
}
|
|
14345
|
-
var init_state_recovery = __esm(() => {
|
|
14346
|
-
init_shared();
|
|
14347
|
-
});
|
|
14348
14369
|
|
|
14349
|
-
// src/
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14370
|
+
// src/commands/machine/daemon-start/handlers/start-agent.ts
|
|
14371
|
+
async function executeStartAgent(ctx, args) {
|
|
14372
|
+
const { chatroomId, role, agentHarness, model, workingDir, reason } = args;
|
|
14373
|
+
console.log(` ↪ start-agent command received`);
|
|
14374
|
+
console.log(` Chatroom: ${chatroomId}`);
|
|
14375
|
+
console.log(` Role: ${role}`);
|
|
14376
|
+
console.log(` Harness: ${agentHarness}`);
|
|
14377
|
+
if (reason) {
|
|
14378
|
+
console.log(` Reason: ${reason}`);
|
|
14379
|
+
}
|
|
14380
|
+
if (model) {
|
|
14381
|
+
console.log(` Model: ${model}`);
|
|
14382
|
+
}
|
|
14383
|
+
if (!workingDir) {
|
|
14384
|
+
const msg2 = `No workingDir provided in command payload for ${chatroomId}/${role}`;
|
|
14385
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14386
|
+
return { result: msg2, failed: true };
|
|
14387
|
+
}
|
|
14388
|
+
console.log(` Working dir: ${workingDir}`);
|
|
14389
|
+
try {
|
|
14390
|
+
const dirStat = await ctx.deps.fs.stat(workingDir);
|
|
14391
|
+
if (!dirStat.isDirectory()) {
|
|
14392
|
+
const msg2 = `Working directory is not a directory: ${workingDir}`;
|
|
14393
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14394
|
+
return { result: msg2, failed: true };
|
|
14355
14395
|
}
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
};
|
|
14396
|
+
} catch {
|
|
14397
|
+
const msg2 = `Working directory does not exist: ${workingDir}`;
|
|
14398
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14399
|
+
return { result: msg2, failed: true };
|
|
14360
14400
|
}
|
|
14361
|
-
|
|
14362
|
-
const
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14401
|
+
try {
|
|
14402
|
+
const existingConfigs = await ctx.deps.backend.query(api.machines.getMachineAgentConfigs, {
|
|
14403
|
+
sessionId: ctx.sessionId,
|
|
14404
|
+
chatroomId
|
|
14405
|
+
});
|
|
14406
|
+
const existingConfig = existingConfigs.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
|
|
14407
|
+
const backendPid = existingConfig?.spawnedAgentPid;
|
|
14408
|
+
const localEntry = ctx.deps.machine.listAgentEntries(ctx.machineId).find((e) => e.chatroomId === chatroomId && e.role.toLowerCase() === role.toLowerCase());
|
|
14409
|
+
const localPid = localEntry?.entry.pid;
|
|
14410
|
+
const pidsToKill = [
|
|
14411
|
+
...new Set([backendPid, localPid].filter((p) => p !== undefined))
|
|
14412
|
+
];
|
|
14413
|
+
const anyService = ctx.agentServices.values().next().value;
|
|
14414
|
+
for (const pid2 of pidsToKill) {
|
|
14415
|
+
const isAlive = anyService ? anyService.isAlive(pid2) : false;
|
|
14416
|
+
if (isAlive) {
|
|
14417
|
+
console.log(` ⚠️ Existing agent detected (PID: ${pid2}) — stopping before respawn`);
|
|
14418
|
+
await onAgentShutdown(ctx, { chatroomId, role, pid: pid2, stopReason: "daemon.respawn" });
|
|
14419
|
+
console.log(` ✅ Existing agent stopped (PID: ${pid2})`);
|
|
14370
14420
|
}
|
|
14371
14421
|
}
|
|
14422
|
+
} catch (e) {
|
|
14423
|
+
console.log(` ⚠️ Could not check for existing agent (proceeding): ${e.message}`);
|
|
14372
14424
|
}
|
|
14373
|
-
|
|
14374
|
-
|
|
14425
|
+
const convexUrl = getConvexUrl();
|
|
14426
|
+
const initPromptResult = await ctx.deps.backend.query(api.messages.getInitPrompt, {
|
|
14427
|
+
sessionId: ctx.sessionId,
|
|
14428
|
+
chatroomId,
|
|
14429
|
+
role,
|
|
14430
|
+
convexUrl
|
|
14431
|
+
});
|
|
14432
|
+
if (!initPromptResult?.prompt) {
|
|
14433
|
+
const msg2 = "Failed to fetch init prompt from backend";
|
|
14434
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14435
|
+
return { result: msg2, failed: true };
|
|
14375
14436
|
}
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
console.log(`[${ts}] Agent stopped: ${stopReason} (${role})`);
|
|
14383
|
-
const isDaemonRespawn = stopReason === "daemon_respawn_stop";
|
|
14384
|
-
const isIntentional = intentional && !isDaemonRespawn;
|
|
14385
|
-
if (isIntentional) {
|
|
14386
|
-
console.log(`[${ts}] ℹ️ Agent process exited after intentional stop ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14387
|
-
} else if (isDaemonRespawn) {
|
|
14388
|
-
console.log(`[${ts}] \uD83D\uDD04 Agent process stopped for respawn ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14389
|
-
} else {
|
|
14390
|
-
console.log(`[${ts}] ⚠️ Agent process exited ` + `(PID: ${pid}, role: ${role}, code: ${code2}, signal: ${signal})`);
|
|
14437
|
+
console.log(` Fetched split init prompt from backend`);
|
|
14438
|
+
const service = ctx.agentServices.get(agentHarness);
|
|
14439
|
+
if (!service) {
|
|
14440
|
+
const msg2 = `Unknown agent harness: ${agentHarness}`;
|
|
14441
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14442
|
+
return { result: msg2, failed: true };
|
|
14391
14443
|
}
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14444
|
+
let spawnResult;
|
|
14445
|
+
try {
|
|
14446
|
+
spawnResult = await service.spawn({
|
|
14447
|
+
workingDir,
|
|
14448
|
+
prompt: initPromptResult.initialMessage,
|
|
14449
|
+
systemPrompt: initPromptResult.rolePrompt,
|
|
14450
|
+
model,
|
|
14451
|
+
context: { machineId: ctx.machineId, chatroomId, role }
|
|
14452
|
+
});
|
|
14453
|
+
} catch (e) {
|
|
14454
|
+
const msg2 = `Failed to spawn agent: ${e.message}`;
|
|
14455
|
+
console.log(` ⚠️ ${msg2}`);
|
|
14456
|
+
return { result: msg2, failed: true };
|
|
14457
|
+
}
|
|
14458
|
+
const { pid } = spawnResult;
|
|
14459
|
+
const msg = `Agent spawned (PID: ${pid})`;
|
|
14460
|
+
console.log(` ✅ ${msg}`);
|
|
14461
|
+
try {
|
|
14462
|
+
await ctx.deps.backend.mutation(api.machines.updateSpawnedAgent, {
|
|
14463
|
+
sessionId: ctx.sessionId,
|
|
14464
|
+
machineId: ctx.machineId,
|
|
14465
|
+
chatroomId,
|
|
14466
|
+
role,
|
|
14467
|
+
pid,
|
|
14468
|
+
model
|
|
14469
|
+
});
|
|
14470
|
+
console.log(` Updated backend with PID: ${pid}`);
|
|
14471
|
+
ctx.deps.machine.persistAgentPid(ctx.machineId, chatroomId, role, pid, agentHarness);
|
|
14472
|
+
} catch (e) {
|
|
14473
|
+
console.log(` ⚠️ Failed to update PID in backend: ${e.message}`);
|
|
14474
|
+
}
|
|
14475
|
+
ctx.events.emit("agent:started", {
|
|
14395
14476
|
chatroomId,
|
|
14396
14477
|
role,
|
|
14397
14478
|
pid,
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
stopSignal: stopReason === "process_terminated_with_signal" ? signal ?? undefined : undefined,
|
|
14401
|
-
exitCode: code2 ?? undefined,
|
|
14402
|
-
signal: signal ?? undefined
|
|
14403
|
-
}).catch((err) => {
|
|
14404
|
-
console.log(` ⚠️ Failed to record agent exit event: ${err.message}`);
|
|
14479
|
+
harness: agentHarness,
|
|
14480
|
+
model
|
|
14405
14481
|
});
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
|
|
14482
|
+
spawnResult.onExit(({ code: code2, signal }) => {
|
|
14483
|
+
const pendingReason = ctx.deps.stops.consume(chatroomId, role);
|
|
14484
|
+
const stopReason = pendingReason ?? resolveStopReason(code2, signal, false);
|
|
14485
|
+
ctx.events.emit("agent:exited", {
|
|
14486
|
+
chatroomId,
|
|
14487
|
+
role,
|
|
14488
|
+
pid,
|
|
14489
|
+
code: code2,
|
|
14490
|
+
signal,
|
|
14491
|
+
stopReason,
|
|
14492
|
+
intentional: pendingReason !== null
|
|
14493
|
+
});
|
|
14494
|
+
});
|
|
14495
|
+
if (spawnResult.onAgentEnd) {
|
|
14496
|
+
spawnResult.onAgentEnd(() => {
|
|
14497
|
+
try {
|
|
14498
|
+
ctx.deps.processes.kill(-pid, "SIGTERM");
|
|
14499
|
+
} catch {}
|
|
14500
|
+
});
|
|
14409
14501
|
}
|
|
14502
|
+
let lastReportedTokenAt = 0;
|
|
14503
|
+
spawnResult.onOutput(() => {
|
|
14504
|
+
const now = Date.now();
|
|
14505
|
+
if (now - lastReportedTokenAt >= 30000) {
|
|
14506
|
+
lastReportedTokenAt = now;
|
|
14507
|
+
ctx.deps.backend.mutation(api.participants.updateTokenActivity, {
|
|
14508
|
+
sessionId: ctx.sessionId,
|
|
14509
|
+
chatroomId,
|
|
14510
|
+
role
|
|
14511
|
+
}).catch(() => {});
|
|
14512
|
+
}
|
|
14513
|
+
});
|
|
14514
|
+
return { result: msg, failed: false };
|
|
14410
14515
|
}
|
|
14411
|
-
var
|
|
14516
|
+
var init_start_agent = __esm(() => {
|
|
14412
14517
|
init_api3();
|
|
14518
|
+
init_client2();
|
|
14413
14519
|
});
|
|
14414
14520
|
|
|
14415
|
-
// src/events/daemon/agent/on-agent
|
|
14416
|
-
function
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
// src/events/daemon/register-listeners.ts
|
|
14430
|
-
function registerEventListeners(ctx) {
|
|
14431
|
-
const unsubs = [];
|
|
14432
|
-
unsubs.push(ctx.events.on("agent:exited", (payload) => onAgentExited(ctx, payload)));
|
|
14433
|
-
unsubs.push(ctx.events.on("agent:started", (payload) => onAgentStarted(ctx, payload)));
|
|
14434
|
-
unsubs.push(ctx.events.on("agent:stopped", (payload) => onAgentStopped(ctx, payload)));
|
|
14435
|
-
return () => {
|
|
14436
|
-
for (const unsub of unsubs) {
|
|
14437
|
-
unsub();
|
|
14438
|
-
}
|
|
14439
|
-
};
|
|
14521
|
+
// src/events/daemon/agent/on-request-start-agent.ts
|
|
14522
|
+
async function onRequestStartAgent(ctx, event) {
|
|
14523
|
+
if (Date.now() > event.deadline) {
|
|
14524
|
+
console.log(`[daemon] ⏰ Skipping expired agent.requestStart for role=${event.role} (deadline passed)`);
|
|
14525
|
+
return;
|
|
14526
|
+
}
|
|
14527
|
+
await executeStartAgent(ctx, {
|
|
14528
|
+
chatroomId: event.chatroomId,
|
|
14529
|
+
role: event.role,
|
|
14530
|
+
agentHarness: event.agentHarness,
|
|
14531
|
+
model: event.model,
|
|
14532
|
+
workingDir: event.workingDir,
|
|
14533
|
+
reason: event.reason
|
|
14534
|
+
});
|
|
14440
14535
|
}
|
|
14441
|
-
var
|
|
14442
|
-
|
|
14443
|
-
init_on_agent_started();
|
|
14444
|
-
init_on_agent_stopped();
|
|
14536
|
+
var init_on_request_start_agent = __esm(() => {
|
|
14537
|
+
init_start_agent();
|
|
14445
14538
|
});
|
|
14446
14539
|
|
|
14447
|
-
// src/commands/machine/daemon-start/
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
const
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14540
|
+
// src/commands/machine/daemon-start/handlers/stop-agent.ts
|
|
14541
|
+
async function executeStopAgent(ctx, args) {
|
|
14542
|
+
const { chatroomId, role, reason } = args;
|
|
14543
|
+
const stopReason = reason;
|
|
14544
|
+
console.log(` ↪ stop-agent command received`);
|
|
14545
|
+
console.log(` Chatroom: ${chatroomId}`);
|
|
14546
|
+
console.log(` Role: ${role}`);
|
|
14547
|
+
console.log(` Reason: ${reason}`);
|
|
14548
|
+
const configsResult = await ctx.deps.backend.query(api.machines.getMachineAgentConfigs, {
|
|
14549
|
+
sessionId: ctx.sessionId,
|
|
14550
|
+
chatroomId
|
|
14551
|
+
});
|
|
14552
|
+
const targetConfig = configsResult.configs.find((c) => c.machineId === ctx.machineId && c.role.toLowerCase() === role.toLowerCase());
|
|
14553
|
+
const backendPid = targetConfig?.spawnedAgentPid;
|
|
14554
|
+
const localEntry = ctx.deps.machine.listAgentEntries(ctx.machineId).find((e) => e.chatroomId === chatroomId && e.role.toLowerCase() === role.toLowerCase());
|
|
14555
|
+
const localPid = localEntry?.entry.pid;
|
|
14556
|
+
const allPids = [...new Set([backendPid, localPid].filter((p) => p !== undefined))];
|
|
14557
|
+
if (allPids.length === 0) {
|
|
14558
|
+
const msg = "No running agent found (no PID recorded)";
|
|
14559
|
+
console.log(` ⚠️ ${msg}`);
|
|
14560
|
+
return { result: msg, failed: true };
|
|
14459
14561
|
}
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
}
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
mark: markIntentionalStop,
|
|
14480
|
-
consume: consumeIntentionalStop,
|
|
14481
|
-
clear: clearIntentionalStop
|
|
14482
|
-
},
|
|
14483
|
-
machine: {
|
|
14484
|
-
clearAgentPid,
|
|
14485
|
-
persistAgentPid,
|
|
14486
|
-
listAgentEntries,
|
|
14487
|
-
persistEventCursor,
|
|
14488
|
-
loadEventCursor
|
|
14489
|
-
},
|
|
14490
|
-
clock: {
|
|
14491
|
-
now: () => Date.now(),
|
|
14492
|
-
delay: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
|
|
14562
|
+
const anyService = ctx.agentServices.values().next().value;
|
|
14563
|
+
let anyKilled = false;
|
|
14564
|
+
let lastError = null;
|
|
14565
|
+
for (const pid of allPids) {
|
|
14566
|
+
console.log(` Stopping agent with PID: ${pid}`);
|
|
14567
|
+
const isAlive = anyService ? anyService.isAlive(pid) : false;
|
|
14568
|
+
if (!isAlive) {
|
|
14569
|
+
console.log(` ⚠️ PID ${pid} not found — process already exited or was never started`);
|
|
14570
|
+
await clearAgentPidEverywhere(ctx, chatroomId, role);
|
|
14571
|
+
console.log(` Cleared stale PID`);
|
|
14572
|
+
try {
|
|
14573
|
+
await ctx.deps.backend.mutation(api.participants.leave, {
|
|
14574
|
+
sessionId: ctx.sessionId,
|
|
14575
|
+
chatroomId,
|
|
14576
|
+
role
|
|
14577
|
+
});
|
|
14578
|
+
console.log(` Removed participant record`);
|
|
14579
|
+
} catch {}
|
|
14580
|
+
continue;
|
|
14493
14581
|
}
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
console.
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
console.error(` • ${url}`);
|
|
14582
|
+
try {
|
|
14583
|
+
const shutdownResult = await onAgentShutdown(ctx, {
|
|
14584
|
+
chatroomId,
|
|
14585
|
+
role,
|
|
14586
|
+
pid,
|
|
14587
|
+
stopReason
|
|
14588
|
+
});
|
|
14589
|
+
const msg = shutdownResult.killed ? `Agent stopped (PID: ${pid})` : `Agent stop attempted (PID: ${pid}) — process may still be running`;
|
|
14590
|
+
console.log(` ${shutdownResult.killed ? "✅" : "⚠️ "} ${msg}`);
|
|
14591
|
+
if (shutdownResult.killed) {
|
|
14592
|
+
anyKilled = true;
|
|
14506
14593
|
}
|
|
14594
|
+
} catch (e) {
|
|
14595
|
+
lastError = e;
|
|
14596
|
+
console.log(` ⚠️ Failed to stop agent (PID: ${pid}): ${e.message}`);
|
|
14507
14597
|
}
|
|
14508
|
-
console.error(`
|
|
14509
|
-
Run: chatroom auth login`);
|
|
14510
|
-
releaseLock();
|
|
14511
|
-
process.exit(1);
|
|
14512
14598
|
}
|
|
14513
|
-
|
|
14599
|
+
if (lastError && !anyKilled) {
|
|
14600
|
+
const msg = `Failed to stop agent: ${lastError.message}`;
|
|
14601
|
+
console.log(` ⚠️ ${msg}`);
|
|
14602
|
+
return { result: msg, failed: true };
|
|
14603
|
+
}
|
|
14604
|
+
if (!anyKilled) {
|
|
14605
|
+
return {
|
|
14606
|
+
result: `All recorded PIDs appear stale (processes not found or belong to different programs)`,
|
|
14607
|
+
failed: true
|
|
14608
|
+
};
|
|
14609
|
+
}
|
|
14610
|
+
const killedCount = allPids.length > 1 ? ` (${allPids.length} PIDs)` : ``;
|
|
14611
|
+
return { result: `Agent stopped${killedCount}`, failed: false };
|
|
14514
14612
|
}
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
|
|
14521
|
-
|
|
14522
|
-
|
|
14613
|
+
var init_stop_agent = __esm(() => {
|
|
14614
|
+
init_api3();
|
|
14615
|
+
init_shared();
|
|
14616
|
+
});
|
|
14617
|
+
|
|
14618
|
+
// src/events/daemon/agent/on-request-stop-agent.ts
|
|
14619
|
+
async function onRequestStopAgent(ctx, event) {
|
|
14620
|
+
if (Date.now() > event.deadline) {
|
|
14621
|
+
console.log(`[daemon] ⏰ Skipping expired agent.requestStop for role=${event.role} (deadline passed)`);
|
|
14622
|
+
return;
|
|
14523
14623
|
}
|
|
14624
|
+
await executeStopAgent(ctx, {
|
|
14625
|
+
chatroomId: event.chatroomId,
|
|
14626
|
+
role: event.role,
|
|
14627
|
+
reason: event.reason
|
|
14628
|
+
});
|
|
14524
14629
|
}
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14630
|
+
var init_on_request_stop_agent = __esm(() => {
|
|
14631
|
+
init_stop_agent();
|
|
14632
|
+
});
|
|
14633
|
+
|
|
14634
|
+
// src/commands/machine/daemon-start/handlers/ping.ts
|
|
14635
|
+
function handlePing() {
|
|
14636
|
+
console.log(` ↪ Responding: pong`);
|
|
14637
|
+
return { result: "pong", failed: false };
|
|
14529
14638
|
}
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14639
|
+
|
|
14640
|
+
// src/commands/machine/daemon-start/command-loop.ts
|
|
14641
|
+
async function refreshModels(ctx) {
|
|
14642
|
+
if (!ctx.config)
|
|
14643
|
+
return;
|
|
14644
|
+
const models = await discoverModels(ctx.agentServices);
|
|
14645
|
+
const freshConfig = ensureMachineRegistered();
|
|
14646
|
+
ctx.config.availableHarnesses = freshConfig.availableHarnesses;
|
|
14647
|
+
ctx.config.harnessVersions = freshConfig.harnessVersions;
|
|
14648
|
+
const totalCount = Object.values(models).flat().length;
|
|
14533
14649
|
try {
|
|
14534
|
-
await
|
|
14535
|
-
sessionId,
|
|
14536
|
-
machineId,
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
harnessVersions: config3.harnessVersions,
|
|
14541
|
-
availableModels
|
|
14650
|
+
await ctx.deps.backend.mutation(api.machines.refreshCapabilities, {
|
|
14651
|
+
sessionId: ctx.sessionId,
|
|
14652
|
+
machineId: ctx.machineId,
|
|
14653
|
+
availableHarnesses: ctx.config.availableHarnesses,
|
|
14654
|
+
harnessVersions: ctx.config.harnessVersions,
|
|
14655
|
+
availableModels: models
|
|
14542
14656
|
});
|
|
14657
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDD04 Model refresh: ${totalCount > 0 ? `${totalCount} models` : "none discovered"}`);
|
|
14543
14658
|
} catch (error) {
|
|
14544
|
-
console.warn(
|
|
14659
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Model refresh failed: ${error.message}`);
|
|
14545
14660
|
}
|
|
14546
|
-
return availableModels;
|
|
14547
14661
|
}
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
connected: true
|
|
14554
|
-
});
|
|
14555
|
-
} catch (error) {
|
|
14556
|
-
if (isNetworkError(error)) {
|
|
14557
|
-
formatConnectivityError(error, convexUrl);
|
|
14558
|
-
} else {
|
|
14559
|
-
console.error(`❌ Failed to update daemon status: ${error.message}`);
|
|
14560
|
-
}
|
|
14561
|
-
releaseLock();
|
|
14562
|
-
process.exit(1);
|
|
14662
|
+
function evictStaleDedupEntries(processedCommandIds, processedPingIds) {
|
|
14663
|
+
const evictBefore = Date.now() - AGENT_REQUEST_DEADLINE_MS;
|
|
14664
|
+
for (const [id, ts] of processedCommandIds) {
|
|
14665
|
+
if (ts < evictBefore)
|
|
14666
|
+
processedCommandIds.delete(id);
|
|
14563
14667
|
}
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
console.log(` CLI version: ${getVersion()}`);
|
|
14568
|
-
console.log(` Machine ID: ${ctx.machineId}`);
|
|
14569
|
-
console.log(` Hostname: ${ctx.config?.hostname ?? "unknown"}`);
|
|
14570
|
-
console.log(` Available harnesses: ${ctx.config?.availableHarnesses.join(", ") || "none"}`);
|
|
14571
|
-
console.log(` Available models: ${Object.keys(availableModels).length > 0 ? `${Object.values(availableModels).flat().length} models across ${Object.keys(availableModels).join(", ")}` : "none discovered"}`);
|
|
14572
|
-
console.log(` PID: ${process.pid}`);
|
|
14573
|
-
}
|
|
14574
|
-
async function recoverState(ctx) {
|
|
14575
|
-
console.log(`
|
|
14576
|
-
[${formatTimestamp()}] \uD83D\uDD04 Recovering agent state...`);
|
|
14577
|
-
try {
|
|
14578
|
-
await recoverAgentState(ctx);
|
|
14579
|
-
} catch (e) {
|
|
14580
|
-
console.log(` ⚠️ Recovery failed: ${e.message}`);
|
|
14581
|
-
console.log(` Continuing with fresh state`);
|
|
14668
|
+
for (const [id, ts] of processedPingIds) {
|
|
14669
|
+
if (ts < evictBefore)
|
|
14670
|
+
processedPingIds.delete(id);
|
|
14582
14671
|
}
|
|
14583
14672
|
}
|
|
14584
|
-
async function
|
|
14585
|
-
|
|
14586
|
-
|
|
14673
|
+
async function dispatchCommandEvent(ctx, event, processedCommandIds, processedPingIds) {
|
|
14674
|
+
const eventId = event._id.toString();
|
|
14675
|
+
if (event.type === "agent.requestStart") {
|
|
14676
|
+
if (processedCommandIds.has(eventId))
|
|
14677
|
+
return;
|
|
14678
|
+
processedCommandIds.set(eventId, Date.now());
|
|
14679
|
+
await onRequestStartAgent(ctx, event);
|
|
14680
|
+
} else if (event.type === "agent.requestStop") {
|
|
14681
|
+
if (processedCommandIds.has(eventId))
|
|
14682
|
+
return;
|
|
14683
|
+
processedCommandIds.set(eventId, Date.now());
|
|
14684
|
+
await onRequestStopAgent(ctx, event);
|
|
14685
|
+
} else if (event.type === "daemon.ping") {
|
|
14686
|
+
if (processedPingIds.has(eventId))
|
|
14687
|
+
return;
|
|
14688
|
+
processedPingIds.set(eventId, Date.now());
|
|
14689
|
+
handlePing();
|
|
14690
|
+
await ctx.deps.backend.mutation(api.machines.ackPing, {
|
|
14691
|
+
sessionId: ctx.sessionId,
|
|
14692
|
+
machineId: ctx.machineId,
|
|
14693
|
+
pingEventId: event._id
|
|
14694
|
+
});
|
|
14587
14695
|
}
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
const
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
const
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
machineId,
|
|
14611
|
-
config: config3,
|
|
14612
|
-
deps,
|
|
14613
|
-
events,
|
|
14614
|
-
agentServices
|
|
14696
|
+
}
|
|
14697
|
+
async function startCommandLoop(ctx) {
|
|
14698
|
+
let heartbeatCount = 0;
|
|
14699
|
+
const heartbeatTimer = setInterval(() => {
|
|
14700
|
+
ctx.deps.backend.mutation(api.machines.daemonHeartbeat, {
|
|
14701
|
+
sessionId: ctx.sessionId,
|
|
14702
|
+
machineId: ctx.machineId
|
|
14703
|
+
}).then(() => {
|
|
14704
|
+
heartbeatCount++;
|
|
14705
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDC93 Daemon heartbeat #${heartbeatCount} OK`);
|
|
14706
|
+
}).catch((err) => {
|
|
14707
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Daemon heartbeat failed: ${err.message}`);
|
|
14708
|
+
});
|
|
14709
|
+
}, DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
14710
|
+
heartbeatTimer.unref();
|
|
14711
|
+
const shutdown = async () => {
|
|
14712
|
+
console.log(`
|
|
14713
|
+
[${formatTimestamp()}] Shutting down...`);
|
|
14714
|
+
clearInterval(heartbeatTimer);
|
|
14715
|
+
await onDaemonShutdown(ctx);
|
|
14716
|
+
releaseLock();
|
|
14717
|
+
process.exit(0);
|
|
14615
14718
|
};
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14719
|
+
process.on("SIGINT", shutdown);
|
|
14720
|
+
process.on("SIGTERM", shutdown);
|
|
14721
|
+
process.on("SIGHUP", shutdown);
|
|
14722
|
+
const wsClient2 = await getConvexWsClient();
|
|
14723
|
+
console.log(`
|
|
14724
|
+
Listening for commands...`);
|
|
14725
|
+
console.log(`Press Ctrl+C to stop
|
|
14726
|
+
`);
|
|
14727
|
+
const processedCommandIds = new Map;
|
|
14728
|
+
const processedPingIds = new Map;
|
|
14729
|
+
wsClient2.onUpdate(api.machines.getCommandEvents, {
|
|
14730
|
+
sessionId: ctx.sessionId,
|
|
14731
|
+
machineId: ctx.machineId
|
|
14732
|
+
}, async (result) => {
|
|
14733
|
+
if (!result.events || result.events.length === 0)
|
|
14734
|
+
return;
|
|
14735
|
+
evictStaleDedupEntries(processedCommandIds, processedPingIds);
|
|
14736
|
+
for (const event of result.events) {
|
|
14737
|
+
try {
|
|
14738
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDCE1 Stream command event: ${event.type}`);
|
|
14739
|
+
await dispatchCommandEvent(ctx, event, processedCommandIds, processedPingIds);
|
|
14740
|
+
} catch (err) {
|
|
14741
|
+
console.error(`[${formatTimestamp()}] ❌ Stream command event failed: ${err.message}`);
|
|
14742
|
+
}
|
|
14743
|
+
}
|
|
14744
|
+
});
|
|
14745
|
+
const modelRefreshTimer = setInterval(() => {
|
|
14746
|
+
refreshModels(ctx).catch((err) => {
|
|
14747
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Model refresh error: ${err.message}`);
|
|
14748
|
+
});
|
|
14749
|
+
}, MODEL_REFRESH_INTERVAL_MS);
|
|
14750
|
+
modelRefreshTimer.unref();
|
|
14751
|
+
return await new Promise(() => {});
|
|
14620
14752
|
}
|
|
14621
|
-
var
|
|
14622
|
-
|
|
14753
|
+
var MODEL_REFRESH_INTERVAL_MS;
|
|
14754
|
+
var init_command_loop = __esm(() => {
|
|
14623
14755
|
init_api3();
|
|
14624
|
-
init_storage();
|
|
14625
14756
|
init_client2();
|
|
14626
14757
|
init_machine();
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
init_version();
|
|
14758
|
+
init_on_daemon_shutdown();
|
|
14759
|
+
init_init2();
|
|
14760
|
+
init_on_request_start_agent();
|
|
14761
|
+
init_on_request_stop_agent();
|
|
14632
14762
|
init_pid();
|
|
14633
|
-
|
|
14763
|
+
MODEL_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
14634
14764
|
});
|
|
14635
14765
|
|
|
14636
14766
|
// src/commands/machine/daemon-start/index.ts
|
|
@@ -14711,8 +14841,8 @@ __export(exports_opencode_install, {
|
|
|
14711
14841
|
});
|
|
14712
14842
|
async function isChatroomInstalledDefault() {
|
|
14713
14843
|
try {
|
|
14714
|
-
const { execSync:
|
|
14715
|
-
|
|
14844
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
14845
|
+
execSync2("chatroom --version", { stdio: "pipe" });
|
|
14716
14846
|
return true;
|
|
14717
14847
|
} catch {
|
|
14718
14848
|
return false;
|