comisai 1.0.6 → 1.0.7
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/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/commands/daemon.js +18 -0
- package/node_modules/@comis/cli/dist/wizard/steps/11-daemon-start.js +92 -14
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/package.json +12 -12
|
@@ -246,6 +246,16 @@ async function startDirectMode() {
|
|
|
246
246
|
process.exit(1);
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
+
/** Check if a systemd unit is already active (does not require sudo). */
|
|
250
|
+
async function isSystemdActive(manager) {
|
|
251
|
+
try {
|
|
252
|
+
const { stdout } = await exec("systemctl", systemctlArgs(manager, "is-active", "comis"), { timeout: 5_000 });
|
|
253
|
+
return stdout.trim() === "active";
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
249
259
|
/** Handle the `daemon start` subcommand. */
|
|
250
260
|
async function handleDaemonStart() {
|
|
251
261
|
try {
|
|
@@ -253,6 +263,10 @@ async function handleDaemonStart() {
|
|
|
253
263
|
switch (manager) {
|
|
254
264
|
case "systemd":
|
|
255
265
|
case "systemd-user": {
|
|
266
|
+
if (await isSystemdActive(manager)) {
|
|
267
|
+
success("Daemon is already running (systemd)");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
256
270
|
const scope = manager === "systemd-user" ? "systemd (user scope)" : "systemd";
|
|
257
271
|
info(`Starting daemon via ${scope}...`);
|
|
258
272
|
await execSystemctl(manager, "start", "comis");
|
|
@@ -284,6 +298,10 @@ async function handleDaemonStop() {
|
|
|
284
298
|
switch (manager) {
|
|
285
299
|
case "systemd":
|
|
286
300
|
case "systemd-user": {
|
|
301
|
+
if (!(await isSystemdActive(manager))) {
|
|
302
|
+
warn("Daemon is not running");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
287
305
|
const scope = manager === "systemd-user" ? "systemd (user scope)" : "systemd";
|
|
288
306
|
info(`Stopping daemon via ${scope}...`);
|
|
289
307
|
await execSystemctl(manager, "stop", "comis");
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
16
|
-
import { spawn } from "node:child_process";
|
|
16
|
+
import { execFile, spawn } from "node:child_process";
|
|
17
17
|
import { existsSync, mkdirSync, writeFileSync, openSync, closeSync, accessSync, constants as fsConstants, } from "node:fs";
|
|
18
18
|
import * as os from "node:os";
|
|
19
|
+
import { promisify } from "node:util";
|
|
19
20
|
import { safePath } from "@comis/core";
|
|
21
|
+
const exec = promisify(execFile);
|
|
20
22
|
import { updateState, sectionSeparator, success as themeSuccess, error as themeError, } from "../index.js";
|
|
21
23
|
// ---------- Constants ----------
|
|
22
24
|
/** Max time to wait for daemon gateway to become ready (ms). */
|
|
@@ -194,6 +196,58 @@ async function runHealthCheck(state, prompter, gatewayHost, gatewayPort) {
|
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
}
|
|
199
|
+
async function detectServiceManager() {
|
|
200
|
+
if (!existsSync("/run/systemd/system"))
|
|
201
|
+
return "direct";
|
|
202
|
+
try {
|
|
203
|
+
const { stdout } = await exec("systemctl", ["list-unit-files", "comis.service", "--no-pager", "--no-legend"], { timeout: 5_000 });
|
|
204
|
+
if (stdout.includes("comis.service"))
|
|
205
|
+
return "systemd";
|
|
206
|
+
}
|
|
207
|
+
catch { /* not available */ }
|
|
208
|
+
try {
|
|
209
|
+
const { stdout } = await exec("systemctl", ["--user", "list-unit-files", "comis.service", "--no-pager", "--no-legend"], { timeout: 5_000 });
|
|
210
|
+
if (stdout.includes("comis.service"))
|
|
211
|
+
return "systemd-user";
|
|
212
|
+
}
|
|
213
|
+
catch { /* not available */ }
|
|
214
|
+
return "direct";
|
|
215
|
+
}
|
|
216
|
+
async function restartViaSystemd(manager) {
|
|
217
|
+
try {
|
|
218
|
+
const args = manager === "systemd-user"
|
|
219
|
+
? ["--user", "restart", "comis"]
|
|
220
|
+
: ["restart", "comis"];
|
|
221
|
+
// System-scope requires root; try sudo for non-root users
|
|
222
|
+
if (manager === "systemd" && process.getuid?.() !== 0) {
|
|
223
|
+
await exec("sudo", ["systemctl", ...args], { timeout: 15_000 });
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
await exec("systemctl", args, { timeout: 15_000 });
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function startViaSystemd(manager) {
|
|
235
|
+
try {
|
|
236
|
+
const args = manager === "systemd-user"
|
|
237
|
+
? ["--user", "start", "comis"]
|
|
238
|
+
: ["start", "comis"];
|
|
239
|
+
if (manager === "systemd" && process.getuid?.() !== 0) {
|
|
240
|
+
await exec("sudo", ["systemctl", ...args], { timeout: 15_000 });
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
await exec("systemctl", args, { timeout: 15_000 });
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
197
251
|
// ---------- Step Implementation ----------
|
|
198
252
|
export const daemonStartStep = {
|
|
199
253
|
id: "daemon-start",
|
|
@@ -216,6 +270,7 @@ export const daemonStartStep = {
|
|
|
216
270
|
catch {
|
|
217
271
|
// Not running
|
|
218
272
|
}
|
|
273
|
+
const serviceManager = await detectServiceManager();
|
|
219
274
|
// 1. Offer auto-start (or restart if already running)
|
|
220
275
|
let choice;
|
|
221
276
|
if (daemonRunning) {
|
|
@@ -239,10 +294,44 @@ export const daemonStartStep = {
|
|
|
239
294
|
}
|
|
240
295
|
// 2. If user declines, show manual command and move on
|
|
241
296
|
if (choice === "no") {
|
|
242
|
-
|
|
297
|
+
if (daemonRunning && serviceManager !== "direct") {
|
|
298
|
+
prompter.log.info("Restart to apply changes: sudo systemctl restart comis");
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
prompter.log.info("Start later with: comis daemon start");
|
|
302
|
+
}
|
|
303
|
+
return updateState(state, {});
|
|
304
|
+
}
|
|
305
|
+
// 3. Use systemd when it owns the daemon
|
|
306
|
+
if (serviceManager !== "direct") {
|
|
307
|
+
const spinner = prompter.spinner();
|
|
308
|
+
const action = choice === "restart" ? "Restarting" : "Starting";
|
|
309
|
+
spinner.start(`${action} daemon via systemd...`);
|
|
310
|
+
const ok = choice === "restart"
|
|
311
|
+
? await restartViaSystemd(serviceManager)
|
|
312
|
+
: await startViaSystemd(serviceManager);
|
|
313
|
+
if (!ok) {
|
|
314
|
+
spinner.stop(`Could not ${choice === "restart" ? "restart" : "start"} via systemd`);
|
|
315
|
+
if (process.getuid?.() !== 0) {
|
|
316
|
+
prompter.log.warn("Run as root: sudo systemctl restart comis");
|
|
317
|
+
}
|
|
318
|
+
return updateState(state, {});
|
|
319
|
+
}
|
|
320
|
+
const ready = await waitForReady(host, port);
|
|
321
|
+
if (ready) {
|
|
322
|
+
spinner.stop(`Daemon ${choice === "restart" ? "restarted" : "started"} and ready`);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
spinner.stop(`Daemon ${choice === "restart" ? "restarted" : "started"} but gateway not yet responding`);
|
|
326
|
+
prompter.log.warn("Check logs: comis daemon logs");
|
|
327
|
+
}
|
|
328
|
+
if (!state.skipHealth) {
|
|
329
|
+
await runHealthCheck(state, prompter, host, port);
|
|
330
|
+
}
|
|
243
331
|
return updateState(state, {});
|
|
244
332
|
}
|
|
245
|
-
//
|
|
333
|
+
// 4. Direct-spawn fallback (no systemd)
|
|
334
|
+
// Stop existing daemon before restart
|
|
246
335
|
if (choice === "restart") {
|
|
247
336
|
const stopSpinner = prompter.spinner();
|
|
248
337
|
stopSpinner.start("Stopping daemon...");
|
|
@@ -256,7 +345,6 @@ export const daemonStartStep = {
|
|
|
256
345
|
process.kill(pid, "SIGTERM");
|
|
257
346
|
}
|
|
258
347
|
catch { /* already stopped */ }
|
|
259
|
-
// Brief wait for graceful shutdown
|
|
260
348
|
await new Promise((r) => setTimeout(r, 1500));
|
|
261
349
|
}
|
|
262
350
|
}
|
|
@@ -266,24 +354,19 @@ export const daemonStartStep = {
|
|
|
266
354
|
stopSpinner.stop("Could not stop daemon (may already be stopped)");
|
|
267
355
|
}
|
|
268
356
|
}
|
|
269
|
-
// 3. Spawn daemon
|
|
270
357
|
const spinner = prompter.spinner();
|
|
271
358
|
spinner.start("Starting daemon...");
|
|
272
359
|
try {
|
|
273
|
-
// Resolve daemon binary path (relative to this file's location in dist/)
|
|
274
360
|
const daemonPath = new URL("../../../../daemon/dist/daemon.js", import.meta.url).pathname;
|
|
275
361
|
if (!existsSync(daemonPath)) {
|
|
276
362
|
spinner.stop("Daemon binary not found");
|
|
277
363
|
prompter.log.warn("Run 'pnpm build' first, then 'comis daemon start'");
|
|
278
364
|
return updateState(state, {});
|
|
279
365
|
}
|
|
280
|
-
// Determine paths using safePath (matching daemon.ts pattern)
|
|
281
366
|
const comisDir = safePath(os.homedir(), ".comis");
|
|
282
367
|
const pidFile = safePath(comisDir, "daemon.pid");
|
|
283
368
|
const logFile = safePath(comisDir, "daemon.log");
|
|
284
|
-
// Ensure directory exists with restricted permissions
|
|
285
369
|
mkdirSync(comisDir, { recursive: true, mode: 0o700 });
|
|
286
|
-
// Open log file for daemon stdout/stderr
|
|
287
370
|
const logFd = openSync(logFile, "a", 0o600);
|
|
288
371
|
let childPid;
|
|
289
372
|
try {
|
|
@@ -295,17 +378,14 @@ export const daemonStartStep = {
|
|
|
295
378
|
childPid = child.pid ?? undefined;
|
|
296
379
|
}
|
|
297
380
|
finally {
|
|
298
|
-
// Close the file descriptor in the parent process after spawn
|
|
299
381
|
closeSync(logFd);
|
|
300
382
|
}
|
|
301
383
|
if (!childPid) {
|
|
302
384
|
spinner.stop("Failed to start daemon: no PID returned");
|
|
303
385
|
return updateState(state, {});
|
|
304
386
|
}
|
|
305
|
-
// Write PID file
|
|
306
387
|
writeFileSync(pidFile, String(childPid));
|
|
307
388
|
spinner.update(`Daemon started (PID ${childPid})`);
|
|
308
|
-
// 4. Wait for readiness
|
|
309
389
|
const ready = await waitForReady(host, port);
|
|
310
390
|
if (ready) {
|
|
311
391
|
spinner.stop(`Daemon started and ready (PID ${childPid})`);
|
|
@@ -314,7 +394,6 @@ export const daemonStartStep = {
|
|
|
314
394
|
spinner.stop("Daemon started but gateway not yet responding");
|
|
315
395
|
prompter.log.warn("Check logs: comis daemon logs");
|
|
316
396
|
}
|
|
317
|
-
// 5. Run health check (skip if --skip-health was passed)
|
|
318
397
|
if (!state.skipHealth) {
|
|
319
398
|
await runHealthCheck(state, prompter, host, port);
|
|
320
399
|
}
|
|
@@ -324,7 +403,6 @@ export const daemonStartStep = {
|
|
|
324
403
|
spinner.stop(`Failed to start daemon: ${msg}`);
|
|
325
404
|
prompter.log.warn("You can start the daemon later with: comis daemon start");
|
|
326
405
|
}
|
|
327
|
-
// 7. Return state
|
|
328
406
|
return updateState(state, {});
|
|
329
407
|
},
|
|
330
408
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comisai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"author": "Moshe Anconina",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Security-first AI agent platform — connects AI agents to Discord, Telegram, Slack, WhatsApp, and more",
|
|
@@ -115,17 +115,17 @@
|
|
|
115
115
|
"@comis/daemon"
|
|
116
116
|
],
|
|
117
117
|
"dependencies": {
|
|
118
|
-
"@comis/shared": "1.0.
|
|
119
|
-
"@comis/core": "1.0.
|
|
120
|
-
"@comis/infra": "1.0.
|
|
121
|
-
"@comis/memory": "1.0.
|
|
122
|
-
"@comis/gateway": "1.0.
|
|
123
|
-
"@comis/skills": "1.0.
|
|
124
|
-
"@comis/scheduler": "1.0.
|
|
125
|
-
"@comis/agent": "1.0.
|
|
126
|
-
"@comis/channels": "1.0.
|
|
127
|
-
"@comis/cli": "1.0.
|
|
128
|
-
"@comis/daemon": "1.0.
|
|
118
|
+
"@comis/shared": "1.0.7",
|
|
119
|
+
"@comis/core": "1.0.7",
|
|
120
|
+
"@comis/infra": "1.0.7",
|
|
121
|
+
"@comis/memory": "1.0.7",
|
|
122
|
+
"@comis/gateway": "1.0.7",
|
|
123
|
+
"@comis/skills": "1.0.7",
|
|
124
|
+
"@comis/scheduler": "1.0.7",
|
|
125
|
+
"@comis/agent": "1.0.7",
|
|
126
|
+
"@comis/channels": "1.0.7",
|
|
127
|
+
"@comis/cli": "1.0.7",
|
|
128
|
+
"@comis/daemon": "1.0.7",
|
|
129
129
|
"@agentclientprotocol/sdk": "^0.15.0",
|
|
130
130
|
"@clack/core": "^1.1.0",
|
|
131
131
|
"@clack/prompts": "^1.1.0",
|