@alook/cli 0.0.51 → 0.0.53
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 +322 -264
- package/dist/session-runner.js +6 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { Command as Command11 } from "commander";
|
|
|
20
20
|
// commands/register.ts
|
|
21
21
|
import { Command } from "commander";
|
|
22
22
|
import { execSync } from "child_process";
|
|
23
|
-
import { hostname } from "os";
|
|
23
|
+
import { hostname as hostname2 } from "os";
|
|
24
24
|
|
|
25
25
|
// lib/client.ts
|
|
26
26
|
class APIClient {
|
|
@@ -164,6 +164,256 @@ function saveCLIConfigForProfile(profile, profileConfig) {
|
|
|
164
164
|
saveCLIConfig(cfg);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// daemon/pidfile.ts
|
|
168
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
169
|
+
import { dirname as dirname2 } from "path";
|
|
170
|
+
|
|
171
|
+
// daemon/config.ts
|
|
172
|
+
import { hostname } from "os";
|
|
173
|
+
import { join as join3 } from "path";
|
|
174
|
+
|
|
175
|
+
// lib/version.ts
|
|
176
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
177
|
+
import { join as join2, dirname } from "path";
|
|
178
|
+
import { fileURLToPath } from "url";
|
|
179
|
+
function getCurrentVersion() {
|
|
180
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
181
|
+
const candidates = [
|
|
182
|
+
join2(__dirname2, "..", "package.json"),
|
|
183
|
+
join2(__dirname2, "..", "..", "package.json")
|
|
184
|
+
];
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
try {
|
|
187
|
+
const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
|
|
188
|
+
if (typeof pkg.version === "string")
|
|
189
|
+
return pkg.version;
|
|
190
|
+
} catch {}
|
|
191
|
+
}
|
|
192
|
+
return "unknown";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// daemon/config.ts
|
|
196
|
+
function pidFilePath(profile) {
|
|
197
|
+
const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
|
|
198
|
+
return join3(configDir(), name);
|
|
199
|
+
}
|
|
200
|
+
function lastUpdateMarkerPath(profile) {
|
|
201
|
+
const name = profile ? `last_update_${profile}` : "last_update";
|
|
202
|
+
return join3(configDir(), name);
|
|
203
|
+
}
|
|
204
|
+
function daemonLogDir() {
|
|
205
|
+
return join3(configDir(), "daemon", "logs");
|
|
206
|
+
}
|
|
207
|
+
function sessionRunnerLogDir() {
|
|
208
|
+
return join3(configDir(), "daemon", "session-runners");
|
|
209
|
+
}
|
|
210
|
+
function daemonLogFilePath(date = new Date) {
|
|
211
|
+
const y = date.getFullYear();
|
|
212
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
213
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
214
|
+
return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
|
|
215
|
+
}
|
|
216
|
+
function parseDuration(s) {
|
|
217
|
+
if (!s)
|
|
218
|
+
return 0;
|
|
219
|
+
let total = 0;
|
|
220
|
+
const regex = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/g;
|
|
221
|
+
let match;
|
|
222
|
+
while ((match = regex.exec(s)) !== null) {
|
|
223
|
+
const val = parseFloat(match[1]);
|
|
224
|
+
switch (match[2]) {
|
|
225
|
+
case "ns":
|
|
226
|
+
total += val / 1e6;
|
|
227
|
+
break;
|
|
228
|
+
case "us":
|
|
229
|
+
case "µs":
|
|
230
|
+
total += val / 1000;
|
|
231
|
+
break;
|
|
232
|
+
case "ms":
|
|
233
|
+
total += val;
|
|
234
|
+
break;
|
|
235
|
+
case "s":
|
|
236
|
+
total += val * 1000;
|
|
237
|
+
break;
|
|
238
|
+
case "m":
|
|
239
|
+
total += val * 60000;
|
|
240
|
+
break;
|
|
241
|
+
case "h":
|
|
242
|
+
total += val * 3600000;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return total;
|
|
247
|
+
}
|
|
248
|
+
function loadDaemonConfig(profile) {
|
|
249
|
+
const h = hostname();
|
|
250
|
+
let daemonId = process.env.ALOOK_DAEMON_ID || h;
|
|
251
|
+
if (profile && !daemonId.endsWith(`-${profile}`)) {
|
|
252
|
+
daemonId = `${daemonId}-${profile}`;
|
|
253
|
+
}
|
|
254
|
+
const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
|
|
255
|
+
const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
|
|
256
|
+
return {
|
|
257
|
+
serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
|
|
258
|
+
claudePath: process.env.ALOOK_CLAUDE_PATH || "claude",
|
|
259
|
+
codexPath: process.env.ALOOK_CODEX_PATH || "codex",
|
|
260
|
+
opencodePath: process.env.ALOOK_OPENCODE_PATH || "opencode",
|
|
261
|
+
claudeModel: process.env.ALOOK_CLAUDE_MODEL || "",
|
|
262
|
+
codexModel: process.env.ALOOK_CODEX_MODEL || "",
|
|
263
|
+
opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
|
|
264
|
+
pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
|
|
265
|
+
agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
|
|
266
|
+
messageInactivityTimeout: parseDuration(process.env.ALOOK_MESSAGE_INACTIVITY_TIMEOUT || "20m"),
|
|
267
|
+
maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
|
|
268
|
+
daemonId,
|
|
269
|
+
deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
|
|
270
|
+
workspacesRoot,
|
|
271
|
+
cliVersion: getCurrentVersion()
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function normalizeServerBaseURL(url) {
|
|
275
|
+
return url.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/ws$/, "");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// lib/logger.ts
|
|
279
|
+
var LEVELS = {
|
|
280
|
+
debug: 0,
|
|
281
|
+
info: 1,
|
|
282
|
+
warn: 2,
|
|
283
|
+
error: 3,
|
|
284
|
+
silent: 4
|
|
285
|
+
};
|
|
286
|
+
var LABELS = {
|
|
287
|
+
debug: "DEBUG",
|
|
288
|
+
info: "INFO ",
|
|
289
|
+
warn: "WARN ",
|
|
290
|
+
error: "ERROR"
|
|
291
|
+
};
|
|
292
|
+
var COLORS = {
|
|
293
|
+
debug: "\x1B[90m",
|
|
294
|
+
info: "\x1B[36m",
|
|
295
|
+
warn: "\x1B[33m",
|
|
296
|
+
error: "\x1B[31m"
|
|
297
|
+
};
|
|
298
|
+
var RESET = "\x1B[0m";
|
|
299
|
+
var DIM = "\x1B[2m";
|
|
300
|
+
function useColor() {
|
|
301
|
+
if (process.env.NO_COLOR !== undefined)
|
|
302
|
+
return false;
|
|
303
|
+
if (process.env.FORCE_COLOR !== undefined)
|
|
304
|
+
return true;
|
|
305
|
+
return process.stdout.isTTY === true;
|
|
306
|
+
}
|
|
307
|
+
function timestamp() {
|
|
308
|
+
const d = new Date;
|
|
309
|
+
const Y = d.getFullYear();
|
|
310
|
+
const M = String(d.getMonth() + 1).padStart(2, "0");
|
|
311
|
+
const D = String(d.getDate()).padStart(2, "0");
|
|
312
|
+
const h = String(d.getHours()).padStart(2, "0");
|
|
313
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
314
|
+
const s = String(d.getSeconds()).padStart(2, "0");
|
|
315
|
+
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
class Logger {
|
|
319
|
+
level;
|
|
320
|
+
color;
|
|
321
|
+
constructor(level = "info") {
|
|
322
|
+
this.level = LEVELS[level];
|
|
323
|
+
this.color = useColor();
|
|
324
|
+
}
|
|
325
|
+
setLevel(level) {
|
|
326
|
+
this.level = LEVELS[level];
|
|
327
|
+
}
|
|
328
|
+
debug(msg, ...args) {
|
|
329
|
+
this.write("debug", msg, args);
|
|
330
|
+
}
|
|
331
|
+
info(msg, ...args) {
|
|
332
|
+
this.write("info", msg, args);
|
|
333
|
+
}
|
|
334
|
+
warn(msg, ...args) {
|
|
335
|
+
this.write("warn", msg, args);
|
|
336
|
+
}
|
|
337
|
+
error(msg, ...args) {
|
|
338
|
+
this.write("error", msg, args);
|
|
339
|
+
}
|
|
340
|
+
write(level, msg, args) {
|
|
341
|
+
if (LEVELS[level] < this.level)
|
|
342
|
+
return;
|
|
343
|
+
const ts = timestamp();
|
|
344
|
+
const label = LABELS[level];
|
|
345
|
+
let line;
|
|
346
|
+
if (this.color) {
|
|
347
|
+
const c = COLORS[level];
|
|
348
|
+
line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
|
|
349
|
+
} else {
|
|
350
|
+
line = `${ts} ${label} ${msg}`;
|
|
351
|
+
}
|
|
352
|
+
const dest = level === "error" ? process.stderr : process.stdout;
|
|
353
|
+
dest.write(line + `
|
|
354
|
+
`);
|
|
355
|
+
for (const a of args) {
|
|
356
|
+
if (a instanceof Error) {
|
|
357
|
+
dest.write(` ${a.message}
|
|
358
|
+
`);
|
|
359
|
+
} else if (a !== undefined) {
|
|
360
|
+
dest.write(` ${String(a)}
|
|
361
|
+
`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function createLogger(level) {
|
|
367
|
+
const envLevel = process.env.ALOOK_LOG_LEVEL;
|
|
368
|
+
return new Logger(level ?? envLevel ?? "info");
|
|
369
|
+
}
|
|
370
|
+
var log = createLogger();
|
|
371
|
+
|
|
372
|
+
// daemon/pidfile.ts
|
|
373
|
+
function isProcessAlive(pid) {
|
|
374
|
+
try {
|
|
375
|
+
process.kill(pid, 0);
|
|
376
|
+
return true;
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function readDaemonPid(profile) {
|
|
382
|
+
try {
|
|
383
|
+
const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
|
|
384
|
+
const pid = parseInt(content, 10);
|
|
385
|
+
return Number.isNaN(pid) ? null : pid;
|
|
386
|
+
} catch {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function acquireDaemonPid(profile) {
|
|
391
|
+
const pidPath = pidFilePath(profile);
|
|
392
|
+
try {
|
|
393
|
+
const content = readFileSync3(pidPath, "utf-8").trim();
|
|
394
|
+
const existingPid = parseInt(content, 10);
|
|
395
|
+
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
396
|
+
log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
} catch {}
|
|
400
|
+
mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
|
|
401
|
+
writeFileSync2(pidPath, String(process.pid), { mode: 384 });
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
function removePidFileIfMatches(pid, profile) {
|
|
405
|
+
const pidPath = pidFilePath(profile);
|
|
406
|
+
const onDisk = readDaemonPid(profile);
|
|
407
|
+
if (onDisk !== pid)
|
|
408
|
+
return;
|
|
409
|
+
try {
|
|
410
|
+
unlinkSync(pidPath);
|
|
411
|
+
} catch {}
|
|
412
|
+
}
|
|
413
|
+
function releaseDaemonPid(profile) {
|
|
414
|
+
removePidFileIfMatches(process.pid, profile);
|
|
415
|
+
}
|
|
416
|
+
|
|
167
417
|
// commands/register.ts
|
|
168
418
|
function isCommandAvailable(cmd) {
|
|
169
419
|
try {
|
|
@@ -227,7 +477,7 @@ Usage: ${cmdPrefix()} register --token <token>`);
|
|
|
227
477
|
process.exit(1);
|
|
228
478
|
}
|
|
229
479
|
console.log(`Found: ${runtimes.map((r) => r.type).join(", ")}`);
|
|
230
|
-
const host =
|
|
480
|
+
const host = hostname2();
|
|
231
481
|
console.log("Registering runtime...");
|
|
232
482
|
let activateResp;
|
|
233
483
|
try {
|
|
@@ -269,8 +519,20 @@ Usage: ${cmdPrefix()} register --token <token>`);
|
|
|
269
519
|
Registered as ${me.email}`);
|
|
270
520
|
console.log(`Workspace: ${ws.name} (${ws.id})`);
|
|
271
521
|
console.log(`Runtimes: ${activateResp.runtimes.map((r) => r.provider).join(", ")}`);
|
|
272
|
-
|
|
273
|
-
|
|
522
|
+
const daemonPid = readDaemonPid(profile);
|
|
523
|
+
if (daemonPid && isProcessAlive(daemonPid)) {
|
|
524
|
+
try {
|
|
525
|
+
process.kill(daemonPid, "SIGHUP");
|
|
526
|
+
console.log(`
|
|
527
|
+
Daemon (pid ${daemonPid}) notified — workspace will be active shortly.`);
|
|
528
|
+
} catch {
|
|
529
|
+
console.log(`
|
|
530
|
+
Daemon is running but could not be notified. Restart it to pick up the new workspace.`);
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
console.log();
|
|
534
|
+
console.log(`Run '${cmdPrefix()} daemon start --foreground' to start the daemon.`);
|
|
535
|
+
}
|
|
274
536
|
});
|
|
275
537
|
return cmd;
|
|
276
538
|
}
|
|
@@ -471,7 +733,7 @@ __export(exports_external, {
|
|
|
471
733
|
instanceof: () => _instanceof,
|
|
472
734
|
includes: () => _includes,
|
|
473
735
|
httpUrl: () => httpUrl,
|
|
474
|
-
hostname: () =>
|
|
736
|
+
hostname: () => hostname4,
|
|
475
737
|
hex: () => hex2,
|
|
476
738
|
hash: () => hash,
|
|
477
739
|
guid: () => guid2,
|
|
@@ -1922,7 +2184,7 @@ __export(exports_regexes, {
|
|
|
1922
2184
|
idnEmail: () => idnEmail,
|
|
1923
2185
|
httpProtocol: () => httpProtocol,
|
|
1924
2186
|
html5Email: () => html5Email,
|
|
1925
|
-
hostname: () =>
|
|
2187
|
+
hostname: () => hostname3,
|
|
1926
2188
|
hex: () => hex,
|
|
1927
2189
|
guid: () => guid,
|
|
1928
2190
|
extendedDuration: () => extendedDuration,
|
|
@@ -1980,7 +2242,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
|
|
|
1980
2242
|
var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
|
|
1981
2243
|
var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
1982
2244
|
var base64url = /^[A-Za-z0-9_-]*$/;
|
|
1983
|
-
var
|
|
2245
|
+
var hostname3 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
|
|
1984
2246
|
var domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
1985
2247
|
var httpProtocol = /^https?$/;
|
|
1986
2248
|
var e164 = /^\+[1-9]\d{6,14}$/;
|
|
@@ -12604,7 +12866,7 @@ __export(exports_schemas2, {
|
|
|
12604
12866
|
int: () => int,
|
|
12605
12867
|
instanceof: () => _instanceof,
|
|
12606
12868
|
httpUrl: () => httpUrl,
|
|
12607
|
-
hostname: () =>
|
|
12869
|
+
hostname: () => hostname4,
|
|
12608
12870
|
hex: () => hex2,
|
|
12609
12871
|
hash: () => hash,
|
|
12610
12872
|
guid: () => guid2,
|
|
@@ -13256,7 +13518,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
|
|
|
13256
13518
|
function stringFormat(format, fnOrRegex, _params = {}) {
|
|
13257
13519
|
return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
|
|
13258
13520
|
}
|
|
13259
|
-
function
|
|
13521
|
+
function hostname4(_params) {
|
|
13260
13522
|
return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
|
|
13261
13523
|
}
|
|
13262
13524
|
function hex2(_params) {
|
|
@@ -14853,7 +15115,7 @@ var IssueStatusSchema = exports_external.enum([
|
|
|
14853
15115
|
IssueStatus.FAILED
|
|
14854
15116
|
]);
|
|
14855
15117
|
var CreateIssueRequestSchema = exports_external.object({
|
|
14856
|
-
agent_id: exports_external.string().min(1
|
|
15118
|
+
agent_id: exports_external.string().min(1).optional(),
|
|
14857
15119
|
title: exports_external.string().min(1, "title is required").max(200),
|
|
14858
15120
|
description: exports_external.string().max(20000).optional().default("")
|
|
14859
15121
|
});
|
|
@@ -14877,9 +15139,9 @@ var IssueCommentApiSchema = exports_external.object({
|
|
|
14877
15139
|
var IssueApiSchema = exports_external.object({
|
|
14878
15140
|
id: exports_external.string(),
|
|
14879
15141
|
workspace_id: exports_external.string(),
|
|
14880
|
-
agent_id: exports_external.string(),
|
|
15142
|
+
agent_id: exports_external.string().nullable(),
|
|
14881
15143
|
creator_user_id: exports_external.string(),
|
|
14882
|
-
conversation_id: exports_external.string(),
|
|
15144
|
+
conversation_id: exports_external.string().nullable(),
|
|
14883
15145
|
latest_task_id: exports_external.string().nullable(),
|
|
14884
15146
|
title: exports_external.string(),
|
|
14885
15147
|
description: exports_external.string(),
|
|
@@ -15655,7 +15917,7 @@ function sql(strings, ...params) {
|
|
|
15655
15917
|
return new SQL([new StringChunk(str)]);
|
|
15656
15918
|
}
|
|
15657
15919
|
sql2.raw = raw;
|
|
15658
|
-
function
|
|
15920
|
+
function join4(chunks, separator) {
|
|
15659
15921
|
const result = [];
|
|
15660
15922
|
for (const [i, chunk] of chunks.entries()) {
|
|
15661
15923
|
if (i > 0 && separator !== undefined) {
|
|
@@ -15665,7 +15927,7 @@ function sql(strings, ...params) {
|
|
|
15665
15927
|
}
|
|
15666
15928
|
return new SQL(result);
|
|
15667
15929
|
}
|
|
15668
|
-
sql2.join =
|
|
15930
|
+
sql2.join = join4;
|
|
15669
15931
|
function identifier(value) {
|
|
15670
15932
|
return new Name(value);
|
|
15671
15933
|
}
|
|
@@ -16689,9 +16951,9 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
|
16689
16951
|
var issue2 = sqliteTable("issue", {
|
|
16690
16952
|
id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
|
|
16691
16953
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
16692
|
-
agentId: text("agent_id")
|
|
16954
|
+
agentId: text("agent_id"),
|
|
16693
16955
|
creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
16694
|
-
conversationId: text("conversation_id").
|
|
16956
|
+
conversationId: text("conversation_id").references(() => conversation.id, { onDelete: "cascade" }),
|
|
16695
16957
|
latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
|
|
16696
16958
|
onDelete: "set null"
|
|
16697
16959
|
}),
|
|
@@ -17073,113 +17335,6 @@ class DaemonClient {
|
|
|
17073
17335
|
}
|
|
17074
17336
|
}
|
|
17075
17337
|
|
|
17076
|
-
// daemon/config.ts
|
|
17077
|
-
import { hostname as hostname4 } from "os";
|
|
17078
|
-
import { join as join3 } from "path";
|
|
17079
|
-
|
|
17080
|
-
// lib/version.ts
|
|
17081
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
17082
|
-
import { join as join2, dirname } from "path";
|
|
17083
|
-
import { fileURLToPath } from "url";
|
|
17084
|
-
function getCurrentVersion() {
|
|
17085
|
-
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
17086
|
-
const candidates = [
|
|
17087
|
-
join2(__dirname2, "..", "package.json"),
|
|
17088
|
-
join2(__dirname2, "..", "..", "package.json")
|
|
17089
|
-
];
|
|
17090
|
-
for (const candidate of candidates) {
|
|
17091
|
-
try {
|
|
17092
|
-
const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
|
|
17093
|
-
if (typeof pkg.version === "string")
|
|
17094
|
-
return pkg.version;
|
|
17095
|
-
} catch {}
|
|
17096
|
-
}
|
|
17097
|
-
return "unknown";
|
|
17098
|
-
}
|
|
17099
|
-
|
|
17100
|
-
// daemon/config.ts
|
|
17101
|
-
function pidFilePath(profile) {
|
|
17102
|
-
const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
|
|
17103
|
-
return join3(configDir(), name);
|
|
17104
|
-
}
|
|
17105
|
-
function lastUpdateMarkerPath(profile) {
|
|
17106
|
-
const name = profile ? `last_update_${profile}` : "last_update";
|
|
17107
|
-
return join3(configDir(), name);
|
|
17108
|
-
}
|
|
17109
|
-
function daemonLogDir() {
|
|
17110
|
-
return join3(configDir(), "daemon", "logs");
|
|
17111
|
-
}
|
|
17112
|
-
function sessionRunnerLogDir() {
|
|
17113
|
-
return join3(configDir(), "daemon", "session-runners");
|
|
17114
|
-
}
|
|
17115
|
-
function daemonLogFilePath(date5 = new Date) {
|
|
17116
|
-
const y = date5.getFullYear();
|
|
17117
|
-
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
17118
|
-
const d = String(date5.getDate()).padStart(2, "0");
|
|
17119
|
-
return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
|
|
17120
|
-
}
|
|
17121
|
-
function parseDuration(s) {
|
|
17122
|
-
if (!s)
|
|
17123
|
-
return 0;
|
|
17124
|
-
let total = 0;
|
|
17125
|
-
const regex = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/g;
|
|
17126
|
-
let match;
|
|
17127
|
-
while ((match = regex.exec(s)) !== null) {
|
|
17128
|
-
const val = parseFloat(match[1]);
|
|
17129
|
-
switch (match[2]) {
|
|
17130
|
-
case "ns":
|
|
17131
|
-
total += val / 1e6;
|
|
17132
|
-
break;
|
|
17133
|
-
case "us":
|
|
17134
|
-
case "µs":
|
|
17135
|
-
total += val / 1000;
|
|
17136
|
-
break;
|
|
17137
|
-
case "ms":
|
|
17138
|
-
total += val;
|
|
17139
|
-
break;
|
|
17140
|
-
case "s":
|
|
17141
|
-
total += val * 1000;
|
|
17142
|
-
break;
|
|
17143
|
-
case "m":
|
|
17144
|
-
total += val * 60000;
|
|
17145
|
-
break;
|
|
17146
|
-
case "h":
|
|
17147
|
-
total += val * 3600000;
|
|
17148
|
-
break;
|
|
17149
|
-
}
|
|
17150
|
-
}
|
|
17151
|
-
return total;
|
|
17152
|
-
}
|
|
17153
|
-
function loadDaemonConfig(profile) {
|
|
17154
|
-
const h = hostname4();
|
|
17155
|
-
let daemonId = process.env.ALOOK_DAEMON_ID || h;
|
|
17156
|
-
if (profile && !daemonId.endsWith(`-${profile}`)) {
|
|
17157
|
-
daemonId = `${daemonId}-${profile}`;
|
|
17158
|
-
}
|
|
17159
|
-
const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
|
|
17160
|
-
const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
|
|
17161
|
-
return {
|
|
17162
|
-
serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
|
|
17163
|
-
claudePath: process.env.ALOOK_CLAUDE_PATH || "claude",
|
|
17164
|
-
codexPath: process.env.ALOOK_CODEX_PATH || "codex",
|
|
17165
|
-
opencodePath: process.env.ALOOK_OPENCODE_PATH || "opencode",
|
|
17166
|
-
claudeModel: process.env.ALOOK_CLAUDE_MODEL || "",
|
|
17167
|
-
codexModel: process.env.ALOOK_CODEX_MODEL || "",
|
|
17168
|
-
opencodeModel: process.env.ALOOK_OPENCODE_MODEL || "",
|
|
17169
|
-
pollInterval: parseDuration(process.env.ALOOK_DAEMON_POLL_INTERVAL || "3s"),
|
|
17170
|
-
agentTimeout: parseDuration(process.env.ALOOK_AGENT_TIMEOUT || "12h"),
|
|
17171
|
-
messageInactivityTimeout: parseDuration(process.env.ALOOK_MESSAGE_INACTIVITY_TIMEOUT || "20m"),
|
|
17172
|
-
maxConcurrentTasks: parseInt(process.env.ALOOK_DAEMON_MAX_CONCURRENT_TASKS || "20"),
|
|
17173
|
-
daemonId,
|
|
17174
|
-
deviceName: process.env.ALOOK_DAEMON_DEVICE_NAME || h,
|
|
17175
|
-
workspacesRoot,
|
|
17176
|
-
cliVersion: getCurrentVersion()
|
|
17177
|
-
};
|
|
17178
|
-
}
|
|
17179
|
-
function normalizeServerBaseURL(url2) {
|
|
17180
|
-
return url2.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/ws$/, "");
|
|
17181
|
-
}
|
|
17182
|
-
|
|
17183
17338
|
// daemon/health.ts
|
|
17184
17339
|
import { createServer } from "http";
|
|
17185
17340
|
var DEFAULT_HEALTH_PORT = Number(process.env.ALOOK_HEALTH_PORT) || 19514;
|
|
@@ -17267,147 +17422,6 @@ function fromApiTask(api2) {
|
|
|
17267
17422
|
};
|
|
17268
17423
|
}
|
|
17269
17424
|
|
|
17270
|
-
// lib/logger.ts
|
|
17271
|
-
var LEVELS = {
|
|
17272
|
-
debug: 0,
|
|
17273
|
-
info: 1,
|
|
17274
|
-
warn: 2,
|
|
17275
|
-
error: 3,
|
|
17276
|
-
silent: 4
|
|
17277
|
-
};
|
|
17278
|
-
var LABELS = {
|
|
17279
|
-
debug: "DEBUG",
|
|
17280
|
-
info: "INFO ",
|
|
17281
|
-
warn: "WARN ",
|
|
17282
|
-
error: "ERROR"
|
|
17283
|
-
};
|
|
17284
|
-
var COLORS = {
|
|
17285
|
-
debug: "\x1B[90m",
|
|
17286
|
-
info: "\x1B[36m",
|
|
17287
|
-
warn: "\x1B[33m",
|
|
17288
|
-
error: "\x1B[31m"
|
|
17289
|
-
};
|
|
17290
|
-
var RESET = "\x1B[0m";
|
|
17291
|
-
var DIM = "\x1B[2m";
|
|
17292
|
-
function useColor() {
|
|
17293
|
-
if (process.env.NO_COLOR !== undefined)
|
|
17294
|
-
return false;
|
|
17295
|
-
if (process.env.FORCE_COLOR !== undefined)
|
|
17296
|
-
return true;
|
|
17297
|
-
return process.stdout.isTTY === true;
|
|
17298
|
-
}
|
|
17299
|
-
function timestamp() {
|
|
17300
|
-
const d = new Date;
|
|
17301
|
-
const Y = d.getFullYear();
|
|
17302
|
-
const M = String(d.getMonth() + 1).padStart(2, "0");
|
|
17303
|
-
const D = String(d.getDate()).padStart(2, "0");
|
|
17304
|
-
const h = String(d.getHours()).padStart(2, "0");
|
|
17305
|
-
const m = String(d.getMinutes()).padStart(2, "0");
|
|
17306
|
-
const s = String(d.getSeconds()).padStart(2, "0");
|
|
17307
|
-
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
|
17308
|
-
}
|
|
17309
|
-
|
|
17310
|
-
class Logger2 {
|
|
17311
|
-
level;
|
|
17312
|
-
color;
|
|
17313
|
-
constructor(level = "info") {
|
|
17314
|
-
this.level = LEVELS[level];
|
|
17315
|
-
this.color = useColor();
|
|
17316
|
-
}
|
|
17317
|
-
setLevel(level) {
|
|
17318
|
-
this.level = LEVELS[level];
|
|
17319
|
-
}
|
|
17320
|
-
debug(msg, ...args) {
|
|
17321
|
-
this.write("debug", msg, args);
|
|
17322
|
-
}
|
|
17323
|
-
info(msg, ...args) {
|
|
17324
|
-
this.write("info", msg, args);
|
|
17325
|
-
}
|
|
17326
|
-
warn(msg, ...args) {
|
|
17327
|
-
this.write("warn", msg, args);
|
|
17328
|
-
}
|
|
17329
|
-
error(msg, ...args) {
|
|
17330
|
-
this.write("error", msg, args);
|
|
17331
|
-
}
|
|
17332
|
-
write(level, msg, args) {
|
|
17333
|
-
if (LEVELS[level] < this.level)
|
|
17334
|
-
return;
|
|
17335
|
-
const ts = timestamp();
|
|
17336
|
-
const label = LABELS[level];
|
|
17337
|
-
let line;
|
|
17338
|
-
if (this.color) {
|
|
17339
|
-
const c = COLORS[level];
|
|
17340
|
-
line = `${DIM}${ts}${RESET} ${c}${label}${RESET} ${msg}`;
|
|
17341
|
-
} else {
|
|
17342
|
-
line = `${ts} ${label} ${msg}`;
|
|
17343
|
-
}
|
|
17344
|
-
const dest = level === "error" ? process.stderr : process.stdout;
|
|
17345
|
-
dest.write(line + `
|
|
17346
|
-
`);
|
|
17347
|
-
for (const a of args) {
|
|
17348
|
-
if (a instanceof Error) {
|
|
17349
|
-
dest.write(` ${a.message}
|
|
17350
|
-
`);
|
|
17351
|
-
} else if (a !== undefined) {
|
|
17352
|
-
dest.write(` ${String(a)}
|
|
17353
|
-
`);
|
|
17354
|
-
}
|
|
17355
|
-
}
|
|
17356
|
-
}
|
|
17357
|
-
}
|
|
17358
|
-
function createLogger2(level) {
|
|
17359
|
-
const envLevel = process.env.ALOOK_LOG_LEVEL;
|
|
17360
|
-
return new Logger2(level ?? envLevel ?? "info");
|
|
17361
|
-
}
|
|
17362
|
-
var log = createLogger2();
|
|
17363
|
-
|
|
17364
|
-
// daemon/pidfile.ts
|
|
17365
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
17366
|
-
import { dirname as dirname2 } from "path";
|
|
17367
|
-
function isProcessAlive(pid) {
|
|
17368
|
-
try {
|
|
17369
|
-
process.kill(pid, 0);
|
|
17370
|
-
return true;
|
|
17371
|
-
} catch {
|
|
17372
|
-
return false;
|
|
17373
|
-
}
|
|
17374
|
-
}
|
|
17375
|
-
function readDaemonPid(profile) {
|
|
17376
|
-
try {
|
|
17377
|
-
const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
|
|
17378
|
-
const pid = parseInt(content, 10);
|
|
17379
|
-
return Number.isNaN(pid) ? null : pid;
|
|
17380
|
-
} catch {
|
|
17381
|
-
return null;
|
|
17382
|
-
}
|
|
17383
|
-
}
|
|
17384
|
-
function acquireDaemonPid(profile) {
|
|
17385
|
-
const pidPath = pidFilePath(profile);
|
|
17386
|
-
try {
|
|
17387
|
-
const content = readFileSync3(pidPath, "utf-8").trim();
|
|
17388
|
-
const existingPid = parseInt(content, 10);
|
|
17389
|
-
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
17390
|
-
log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
|
|
17391
|
-
return false;
|
|
17392
|
-
}
|
|
17393
|
-
} catch {}
|
|
17394
|
-
mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
|
|
17395
|
-
writeFileSync2(pidPath, String(process.pid), { mode: 384 });
|
|
17396
|
-
return true;
|
|
17397
|
-
}
|
|
17398
|
-
function removePidFileIfMatches(pid, profile) {
|
|
17399
|
-
const pidPath = pidFilePath(profile);
|
|
17400
|
-
const onDisk = readDaemonPid(profile);
|
|
17401
|
-
if (onDisk !== pid)
|
|
17402
|
-
return;
|
|
17403
|
-
try {
|
|
17404
|
-
unlinkSync(pidPath);
|
|
17405
|
-
} catch {}
|
|
17406
|
-
}
|
|
17407
|
-
function releaseDaemonPid(profile) {
|
|
17408
|
-
removePidFileIfMatches(process.pid, profile);
|
|
17409
|
-
}
|
|
17410
|
-
|
|
17411
17425
|
// daemon/update-handler.ts
|
|
17412
17426
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
17413
17427
|
|
|
@@ -18217,6 +18231,50 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18217
18231
|
};
|
|
18218
18232
|
process.on("SIGTERM", shutdown);
|
|
18219
18233
|
process.on("SIGINT", shutdown);
|
|
18234
|
+
process.on("SIGHUP", async () => {
|
|
18235
|
+
if (shuttingDown)
|
|
18236
|
+
return;
|
|
18237
|
+
log.info("SIGHUP received — reloading config...");
|
|
18238
|
+
try {
|
|
18239
|
+
const freshConfig = loadCLIConfigForProfile(profile);
|
|
18240
|
+
const freshWorkspaces = freshConfig.watched_workspaces || [];
|
|
18241
|
+
const existingIds = new Set(workspaceStates.map((ws) => ws.workspaceId));
|
|
18242
|
+
const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
|
|
18243
|
+
for (const ws of newWorkspaces) {
|
|
18244
|
+
const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
|
|
18245
|
+
log.info(`Registering new workspace ${ws.id} (${ws.name ?? "unnamed"})...`);
|
|
18246
|
+
try {
|
|
18247
|
+
const resp = await client.register(ws.token, {
|
|
18248
|
+
workspace_id: ws.id,
|
|
18249
|
+
daemon_id: config2.daemonId,
|
|
18250
|
+
device_name: config2.deviceName,
|
|
18251
|
+
cli_version: config2.cliVersion,
|
|
18252
|
+
runtimes
|
|
18253
|
+
});
|
|
18254
|
+
const runtimeIds = resp.runtimes.map((r) => r.id);
|
|
18255
|
+
workspaceStates.push({ workspaceId: ws.id, token: ws.token, runtimeIds });
|
|
18256
|
+
for (let i = 0;i < runtimeIds.length; i++) {
|
|
18257
|
+
runtimeIndex.set(runtimeIds[i], {
|
|
18258
|
+
id: runtimeIds[i],
|
|
18259
|
+
workspaceId: ws.id,
|
|
18260
|
+
provider: providers[i].type
|
|
18261
|
+
});
|
|
18262
|
+
}
|
|
18263
|
+
log.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
|
|
18264
|
+
} catch (e) {
|
|
18265
|
+
log.error(`Failed to register new workspace ${ws.id}`, e);
|
|
18266
|
+
}
|
|
18267
|
+
}
|
|
18268
|
+
if (newWorkspaces.length > 0) {
|
|
18269
|
+
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
18270
|
+
log.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
|
|
18271
|
+
} else {
|
|
18272
|
+
log.info("Reload complete — no new workspaces found");
|
|
18273
|
+
}
|
|
18274
|
+
} catch (e) {
|
|
18275
|
+
log.error("Failed to reload config", e);
|
|
18276
|
+
}
|
|
18277
|
+
});
|
|
18220
18278
|
await pollCycle();
|
|
18221
18279
|
}
|
|
18222
18280
|
function spawnSessionRunner(input) {
|
package/dist/session-runner.js
CHANGED
|
@@ -14570,7 +14570,7 @@ var IssueStatusSchema = exports_external.enum([
|
|
|
14570
14570
|
IssueStatus.FAILED
|
|
14571
14571
|
]);
|
|
14572
14572
|
var CreateIssueRequestSchema = exports_external.object({
|
|
14573
|
-
agent_id: exports_external.string().min(1
|
|
14573
|
+
agent_id: exports_external.string().min(1).optional(),
|
|
14574
14574
|
title: exports_external.string().min(1, "title is required").max(200),
|
|
14575
14575
|
description: exports_external.string().max(20000).optional().default("")
|
|
14576
14576
|
});
|
|
@@ -14594,9 +14594,9 @@ var IssueCommentApiSchema = exports_external.object({
|
|
|
14594
14594
|
var IssueApiSchema = exports_external.object({
|
|
14595
14595
|
id: exports_external.string(),
|
|
14596
14596
|
workspace_id: exports_external.string(),
|
|
14597
|
-
agent_id: exports_external.string(),
|
|
14597
|
+
agent_id: exports_external.string().nullable(),
|
|
14598
14598
|
creator_user_id: exports_external.string(),
|
|
14599
|
-
conversation_id: exports_external.string(),
|
|
14599
|
+
conversation_id: exports_external.string().nullable(),
|
|
14600
14600
|
latest_task_id: exports_external.string().nullable(),
|
|
14601
14601
|
title: exports_external.string(),
|
|
14602
14602
|
description: exports_external.string(),
|
|
@@ -16406,9 +16406,9 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
|
16406
16406
|
var issue2 = sqliteTable("issue", {
|
|
16407
16407
|
id: text("id").primaryKey().$defaultFn(() => "iss_" + nanoid3()),
|
|
16408
16408
|
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
16409
|
-
agentId: text("agent_id")
|
|
16409
|
+
agentId: text("agent_id"),
|
|
16410
16410
|
creatorUserId: text("creator_user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
16411
|
-
conversationId: text("conversation_id").
|
|
16411
|
+
conversationId: text("conversation_id").references(() => conversation.id, { onDelete: "cascade" }),
|
|
16412
16412
|
latestTaskId: text("latest_task_id").references(() => agentTaskQueue.id, {
|
|
16413
16413
|
onDelete: "set null"
|
|
16414
16414
|
}),
|
|
@@ -18380,7 +18380,7 @@ function clearKillIntent(baseDir, taskId) {
|
|
|
18380
18380
|
// daemon/prompt.ts
|
|
18381
18381
|
var DM_RESPONSE_NOTICE = "IMPORTANT: Only your final text response is visible to the user." + " Tool calls, intermediate reasoning, and mid-process outputs are NOT displayed." + " Put all key information, answers, and conclusions in your final response — that is the only thing the user will read.";
|
|
18382
18382
|
var EMAIL_NOTICE = "This task was triggered automatically by an incoming email. There is no human in this session." + " If you need to communicate with a human, you MUST send an email using the email sending tool." + " If you need more information or confirmation from the human, send them an email asking for it and then exit." + " Do not wait — when the human replies, a new task will be triggered automatically and you will be woken up with their response.";
|
|
18383
|
-
var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + " You are responsible for setting the issue status: move to in_progress when working
|
|
18383
|
+
var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --agent_id <your_agent_id> --issue_id <issue_id>` to read full context." + " Use `alook issue update --agent_id <your_agent_id> --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --agent_id <your_agent_id> --issue_id <issue_id> --body <text>` to leave a comment." + " You are responsible for setting the issue status: move to in_progress when you start working on the task." + " IMPORTANT: You MUST move the status to 'review' when your task is fully complete — this signals to the owner that your work is ready for review." + " Only set 'review' when the task is done, never prematurely. Do not set 'review' for partial work or while still in progress." + " Always leave a comment summarizing what you did before changing status.";
|
|
18384
18384
|
function buildDmNotice(name, email3) {
|
|
18385
18385
|
return `This task was triggered by an incoming email on a conversation with ${name} (${email3}).` + ` ${name} is present in this session — reply to them directly.` + ` If you need to communicate with anyone else, use the email sending tool.`;
|
|
18386
18386
|
}
|