@alook/daemon 0.0.154 → 0.0.155
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/cli/index.js +252 -69
- package/dist/index.js +6 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Command, CommanderError } from "commander";
|
|
|
7
7
|
|
|
8
8
|
// src/cli/proxyServerApi.ts
|
|
9
9
|
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
10
11
|
function proxyServerApiFromEnv(prefix = "ALOOK", env = process.env) {
|
|
11
12
|
const proxyUrl = env[`${prefix}_PROXY_URL`];
|
|
12
13
|
const tokenFile = env[`${prefix}_PROXY_TOKEN_FILE`];
|
|
@@ -37,6 +38,58 @@ function createProxyServerApi(config) {
|
|
|
37
38
|
}
|
|
38
39
|
return json;
|
|
39
40
|
}
|
|
41
|
+
async function callUpload(req) {
|
|
42
|
+
const form = new FormData;
|
|
43
|
+
const blobType = req.file.contentType ?? "application/octet-stream";
|
|
44
|
+
const bytes = req.file.data instanceof Uint8Array ? new Blob([new Uint8Array(req.file.data)], { type: blobType }) : req.file.data;
|
|
45
|
+
form.append("file", bytes, req.file.filename);
|
|
46
|
+
const url = `${base}/api/attachmentUpload?target=${encodeURIComponent(req.target)}`;
|
|
47
|
+
const res = await fetchImpl(url, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { authorization: `Bearer ${config.voucher}` },
|
|
50
|
+
body: form
|
|
51
|
+
});
|
|
52
|
+
const json = await res.json();
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const e = new Error(json?.error ?? `proxy api/attachmentUpload failed (${res.status})`);
|
|
55
|
+
e.code = json?.code;
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
return json;
|
|
59
|
+
}
|
|
60
|
+
async function callDownload(req) {
|
|
61
|
+
const res = await fetchImpl(`${base}/api/attachmentDownload`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: {
|
|
64
|
+
"content-type": "application/json",
|
|
65
|
+
authorization: `Bearer ${config.voucher}`
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({ id: req.id })
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
const body = await res.json().catch(() => ({}));
|
|
71
|
+
const e = new Error(body?.error ?? `proxy api/attachmentDownload failed (${res.status})`);
|
|
72
|
+
e.code = body?.code;
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
const encoded = res.headers.get("x-alook-filename");
|
|
76
|
+
const filename = encoded ? decodeURIComponent(encoded) : path.basename(req.destPath);
|
|
77
|
+
const contentType = res.headers.get("content-type") || "application/octet-stream";
|
|
78
|
+
const size = Number(res.headers.get("content-length") ?? "0");
|
|
79
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
80
|
+
fs.mkdirSync(path.dirname(req.destPath), { recursive: true });
|
|
81
|
+
const tmp = `${req.destPath}.tmp`;
|
|
82
|
+
try {
|
|
83
|
+
fs.writeFileSync(tmp, buf);
|
|
84
|
+
fs.renameSync(tmp, req.destPath);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
try {
|
|
87
|
+
fs.rmSync(tmp, { force: true });
|
|
88
|
+
} catch {}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
return { path: req.destPath, filename, contentType, size: size || buf.byteLength };
|
|
92
|
+
}
|
|
40
93
|
return {
|
|
41
94
|
listServers: (r) => call("listServers", r),
|
|
42
95
|
listChannels: (r) => call("listChannels", r),
|
|
@@ -47,13 +100,15 @@ function createProxyServerApi(config) {
|
|
|
47
100
|
read: (r) => call("read", r),
|
|
48
101
|
resolve: (r) => call("resolve", r),
|
|
49
102
|
listMembers: (r) => call("listMembers", r),
|
|
50
|
-
joinServer: (r) => call("joinServer", r)
|
|
103
|
+
joinServer: (r) => call("joinServer", r),
|
|
104
|
+
attachmentUpload: callUpload,
|
|
105
|
+
attachmentDownload: callDownload
|
|
51
106
|
};
|
|
52
107
|
}
|
|
53
108
|
|
|
54
109
|
// src/cli/daemonStart.ts
|
|
55
110
|
import * as fs9 from "fs";
|
|
56
|
-
import * as
|
|
111
|
+
import * as path11 from "path";
|
|
57
112
|
import * as crypto2 from "crypto";
|
|
58
113
|
import * as os3 from "os";
|
|
59
114
|
import { homedir as homedir3 } from "os";
|
|
@@ -307,7 +362,7 @@ import * as fs2 from "fs";
|
|
|
307
362
|
import * as http from "http";
|
|
308
363
|
import * as https from "https";
|
|
309
364
|
import * as os from "os";
|
|
310
|
-
import * as
|
|
365
|
+
import * as path2 from "path";
|
|
311
366
|
import { URL } from "url";
|
|
312
367
|
var DEFAULT_HEADER_NAMES = {
|
|
313
368
|
agentId: "X-Agent-Id",
|
|
@@ -335,15 +390,15 @@ class CredentialBroker {
|
|
|
335
390
|
this.voucherPrefix = config.voucherPrefix ?? "vch_";
|
|
336
391
|
this.clientLabel = config.clientLabel ?? "cli";
|
|
337
392
|
this.headerNames = config.headerNames ?? DEFAULT_HEADER_NAMES;
|
|
338
|
-
this.voucherDir = config.voucherDir ??
|
|
393
|
+
this.voucherDir = config.voucherDir ?? path2.join(os.tmpdir(), "agent-vouchers");
|
|
339
394
|
}
|
|
340
395
|
mint(agentId, launchId, capabilities, runnerKey) {
|
|
341
396
|
if (!runnerKey)
|
|
342
397
|
throw new Error("CredentialBroker.mint: runnerKey is required (per-agent tier-2 credential)");
|
|
343
398
|
const voucher = randomVoucher(this.voucherPrefix);
|
|
344
|
-
const dir =
|
|
399
|
+
const dir = path2.join(this.voucherDir, sanitizeIdSegment(agentId));
|
|
345
400
|
fs2.mkdirSync(dir, { recursive: true });
|
|
346
|
-
const voucherFile =
|
|
401
|
+
const voucherFile = path2.join(dir, `${sanitizeIdSegment(launchId)}.token`);
|
|
347
402
|
fs2.writeFileSync(voucherFile, voucher, { mode: 384 });
|
|
348
403
|
this.registrations.set(voucher, {
|
|
349
404
|
agentId,
|
|
@@ -402,6 +457,8 @@ function parseBearer(authHeader) {
|
|
|
402
457
|
return m ? m[1].trim() : null;
|
|
403
458
|
}
|
|
404
459
|
var DEFAULT_CAPABILITY_RESOLVER = (_method, pathname) => {
|
|
460
|
+
if (pathname.includes("/attachment"))
|
|
461
|
+
return "attach";
|
|
405
462
|
if (pathname.includes("/send"))
|
|
406
463
|
return "send";
|
|
407
464
|
if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
|
|
@@ -429,7 +486,7 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
429
486
|
return;
|
|
430
487
|
}
|
|
431
488
|
const reg = verdict.reg;
|
|
432
|
-
const isInboxPull = onPull && pathname
|
|
489
|
+
const isInboxPull = onPull && pathname === "/api/inboxPull";
|
|
433
490
|
if (onProxyRequest) {
|
|
434
491
|
try {
|
|
435
492
|
onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
|
|
@@ -1664,7 +1721,7 @@ class AgentRouter {
|
|
|
1664
1721
|
}
|
|
1665
1722
|
}
|
|
1666
1723
|
// src/timeline/timeline.ts
|
|
1667
|
-
import { appendFileSync, readFileSync as readFileSync3, writeFileSync as
|
|
1724
|
+
import { appendFileSync, readFileSync as readFileSync3, writeFileSync as writeFileSync4, renameSync as renameSync2, existsSync } from "fs";
|
|
1668
1725
|
import { join as join2 } from "path";
|
|
1669
1726
|
|
|
1670
1727
|
// src/timeline/filelock.ts
|
|
@@ -1790,10 +1847,10 @@ function appendOrMergeEntry(timelineDir, entry, now = new Date) {
|
|
|
1790
1847
|
latest.messages = [...latest.messages, ...entry.messages];
|
|
1791
1848
|
lines[lines.length - 1] = JSON.stringify(latest);
|
|
1792
1849
|
const tmpPath = join2(timelineDir, `.${filename}.tmp`);
|
|
1793
|
-
|
|
1850
|
+
writeFileSync4(tmpPath, lines.join(`
|
|
1794
1851
|
`) + `
|
|
1795
1852
|
`);
|
|
1796
|
-
|
|
1853
|
+
renameSync2(tmpPath, filePath);
|
|
1797
1854
|
return true;
|
|
1798
1855
|
}
|
|
1799
1856
|
}
|
|
@@ -1830,10 +1887,10 @@ function updateLatestEntry(timelineDir, updater, opts = {}) {
|
|
|
1830
1887
|
const entries = lines.map((l) => JSON.parse(l));
|
|
1831
1888
|
updater(entries[entries.length - 1]);
|
|
1832
1889
|
const tmpPath = join2(timelineDir, `.${filename}.tmp`);
|
|
1833
|
-
|
|
1890
|
+
writeFileSync4(tmpPath, entries.map((e) => JSON.stringify(e)).join(`
|
|
1834
1891
|
`) + `
|
|
1835
1892
|
`);
|
|
1836
|
-
|
|
1893
|
+
renameSync2(tmpPath, filePath);
|
|
1837
1894
|
return true;
|
|
1838
1895
|
} catch {} finally {
|
|
1839
1896
|
releaseLock(lockPath);
|
|
@@ -1861,7 +1918,7 @@ function findResumableSession(rows, provider) {
|
|
|
1861
1918
|
return null;
|
|
1862
1919
|
}
|
|
1863
1920
|
// src/timeline/recorder.ts
|
|
1864
|
-
import { mkdirSync as
|
|
1921
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1865
1922
|
function createTimelineRecorder(opts) {
|
|
1866
1923
|
const now = opts.now ?? (() => new Date);
|
|
1867
1924
|
const dirFor = (agentId) => opts.timelineDirFor(agentId);
|
|
@@ -1873,7 +1930,7 @@ function createTimelineRecorder(opts) {
|
|
|
1873
1930
|
appendEntryForAgent(agentId, messages) {
|
|
1874
1931
|
const dir = dirFor(agentId);
|
|
1875
1932
|
try {
|
|
1876
|
-
|
|
1933
|
+
mkdirSync4(dir, { recursive: true });
|
|
1877
1934
|
} catch {}
|
|
1878
1935
|
appendOrMergeEntry(dir, createTimelineEntry({
|
|
1879
1936
|
messages,
|
|
@@ -1891,13 +1948,13 @@ function createTimelineRecorder(opts) {
|
|
|
1891
1948
|
};
|
|
1892
1949
|
}
|
|
1893
1950
|
// src/discovery.ts
|
|
1894
|
-
import * as
|
|
1951
|
+
import * as path9 from "path";
|
|
1895
1952
|
import * as fs8 from "fs";
|
|
1896
1953
|
import { fileURLToPath } from "url";
|
|
1897
1954
|
|
|
1898
1955
|
// src/drivers/cliTransport.ts
|
|
1899
1956
|
import * as fs5 from "fs";
|
|
1900
|
-
import * as
|
|
1957
|
+
import * as path4 from "path";
|
|
1901
1958
|
|
|
1902
1959
|
// src/drivers/systemPrompt.ts
|
|
1903
1960
|
var CLI = "alook";
|
|
@@ -1928,7 +1985,9 @@ function cliCommandsSection() {
|
|
|
1928
1985
|
"### Messaging",
|
|
1929
1986
|
"",
|
|
1930
1987
|
`1. \`${CLI} inbox pull\` — fetch unread messages.`,
|
|
1931
|
-
`2. \`${CLI} message send\` — send a message to a channel, DM, or thread.`,
|
|
1988
|
+
`2. \`${CLI} message send\` — send a message to a channel, DM, or thread. ` + `Attach files with \`--attachment <id>\` (repeatable, order matters).`,
|
|
1989
|
+
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a local file; ` + `returns an id. Feed that id into \`message send --attachment <id>\`. ` + `The id is stable across the pending→persisted lifecycle.`,
|
|
1990
|
+
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download an attachment ` + `id from any message you have access to (or your own pending uploads).`,
|
|
1932
1991
|
"",
|
|
1933
1992
|
"### Servers",
|
|
1934
1993
|
"",
|
|
@@ -2173,21 +2232,21 @@ function resolveLaunchFields(config) {
|
|
|
2173
2232
|
|
|
2174
2233
|
// src/drivers/cliLink.ts
|
|
2175
2234
|
import * as fs4 from "fs";
|
|
2176
|
-
import * as
|
|
2235
|
+
import * as path3 from "path";
|
|
2177
2236
|
function writeCliLink(stateDir, cliName, hostCliPath, platform = process.platform) {
|
|
2178
|
-
const binDir =
|
|
2237
|
+
const binDir = path3.join(stateDir, "bin");
|
|
2179
2238
|
fs4.mkdirSync(binDir, { recursive: true });
|
|
2180
2239
|
if (!hostCliPath)
|
|
2181
2240
|
return binDir;
|
|
2182
2241
|
if (platform === "win32") {
|
|
2183
|
-
const cmdFile =
|
|
2242
|
+
const cmdFile = path3.join(binDir, `${cliName}.cmd`);
|
|
2184
2243
|
const body = `@echo off\r
|
|
2185
2244
|
"${hostCliPath}" %*\r
|
|
2186
2245
|
`;
|
|
2187
2246
|
fs4.writeFileSync(cmdFile, body);
|
|
2188
2247
|
return binDir;
|
|
2189
2248
|
}
|
|
2190
|
-
const linkPath =
|
|
2249
|
+
const linkPath = path3.join(binDir, cliName);
|
|
2191
2250
|
try {
|
|
2192
2251
|
fs4.unlinkSync(linkPath);
|
|
2193
2252
|
} catch (err) {
|
|
@@ -2251,7 +2310,7 @@ function runtimeContextEnv(prefix, rc) {
|
|
|
2251
2310
|
|
|
2252
2311
|
// src/drivers/agentFile.ts
|
|
2253
2312
|
import {
|
|
2254
|
-
writeFileSync as
|
|
2313
|
+
writeFileSync as writeFileSync6,
|
|
2255
2314
|
readFileSync as readFileSync4,
|
|
2256
2315
|
lstatSync,
|
|
2257
2316
|
symlinkSync as symlinkSync2,
|
|
@@ -2319,7 +2378,7 @@ function writeAgentFile(workDir, systemPromptContent) {
|
|
|
2319
2378
|
const filePath = join4(workDir, CANONICAL_FILE);
|
|
2320
2379
|
const changed = hasContentChanged(filePath, systemPromptContent);
|
|
2321
2380
|
if (changed) {
|
|
2322
|
-
|
|
2381
|
+
writeFileSync6(filePath, systemPromptContent, "utf-8");
|
|
2323
2382
|
}
|
|
2324
2383
|
ensureSymlinks(workDir);
|
|
2325
2384
|
return changed;
|
|
@@ -2342,13 +2401,13 @@ var DEFAULT_CLI_CONFIG = {
|
|
|
2342
2401
|
stateDirName: ".alook"
|
|
2343
2402
|
};
|
|
2344
2403
|
function resolveStateHome(envPrefix) {
|
|
2345
|
-
return process.env[`${envPrefix}_HOME`] ||
|
|
2404
|
+
return process.env[`${envPrefix}_HOME`] || path4.join(process.env.HOME || process.env.USERPROFILE || ".", `.${envPrefix.toLowerCase()}`);
|
|
2346
2405
|
}
|
|
2347
2406
|
async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG, platform = process.platform) {
|
|
2348
2407
|
const E = cli.envPrefix;
|
|
2349
2408
|
const stateHome = resolveStateHome(E);
|
|
2350
2409
|
const capabilities = cli.activeCapabilities ?? DEFAULT_ACTIVE_CAPABILITIES;
|
|
2351
|
-
const stateDir =
|
|
2410
|
+
const stateDir = path4.join(ctx.workingDirectory, cli.stateDirName);
|
|
2352
2411
|
await fs5.promises.mkdir(stateDir, { recursive: true });
|
|
2353
2412
|
if (ctx.standingPrompt)
|
|
2354
2413
|
writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
|
|
@@ -2360,7 +2419,7 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
|
|
|
2360
2419
|
const reg = ctx.credentialProxy.broker.mint(ctx.agentId, ctx.launchId ?? "default", capabilities, ctx.credentialProxy.runnerKey);
|
|
2361
2420
|
const tokenFile = reg.voucherFile;
|
|
2362
2421
|
const resolved = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
2363
|
-
const pathValue = [binDir, process.env.PATH ?? ""].filter(Boolean).join(
|
|
2422
|
+
const pathValue = [binDir, process.env.PATH ?? ""].filter(Boolean).join(path4.delimiter);
|
|
2364
2423
|
const layers = [
|
|
2365
2424
|
{ name: "hostStatic", precedence: 10, vars: cli.extraEnv ?? {} },
|
|
2366
2425
|
{ name: "userEnv", precedence: 20, vars: resolved.envVars },
|
|
@@ -2404,19 +2463,19 @@ function buildCliTransportSystemPrompt(config, opts) {
|
|
|
2404
2463
|
|
|
2405
2464
|
// src/drivers/claudeProviderIsolation.ts
|
|
2406
2465
|
import * as fs6 from "fs";
|
|
2407
|
-
import * as
|
|
2466
|
+
import * as path5 from "path";
|
|
2408
2467
|
function buildClaudeProviderIsolationEnv(ctx) {
|
|
2409
2468
|
const hasCustomProvider = Boolean(process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_API_KEY);
|
|
2410
2469
|
if (!hasCustomProvider)
|
|
2411
2470
|
return {};
|
|
2412
|
-
const root =
|
|
2413
|
-
const home =
|
|
2414
|
-
const configDir =
|
|
2471
|
+
const root = path5.join(ctx.workingDirectory, ".alook", "claude-provider");
|
|
2472
|
+
const home = path5.join(root, "home");
|
|
2473
|
+
const configDir = path5.join(home, ".claude");
|
|
2415
2474
|
fs6.mkdirSync(configDir, { recursive: true });
|
|
2416
|
-
const hostClaude =
|
|
2475
|
+
const hostClaude = path5.join(process.env.HOME || ".", ".claude");
|
|
2417
2476
|
for (const sub of ["skills", "commands"]) {
|
|
2418
|
-
const target =
|
|
2419
|
-
const link =
|
|
2477
|
+
const target = path5.join(hostClaude, sub);
|
|
2478
|
+
const link = path5.join(configDir, sub);
|
|
2420
2479
|
try {
|
|
2421
2480
|
if (fs6.existsSync(target) && !fs6.existsSync(link))
|
|
2422
2481
|
fs6.symlinkSync(target, link);
|
|
@@ -2433,7 +2492,7 @@ function buildClaudeProviderIsolationEnv(ctx) {
|
|
|
2433
2492
|
// src/drivers/probe.ts
|
|
2434
2493
|
import { execFileSync } from "child_process";
|
|
2435
2494
|
import * as fs7 from "fs";
|
|
2436
|
-
import * as
|
|
2495
|
+
import * as path6 from "path";
|
|
2437
2496
|
function resolveCommandOnPath(command, deps = {}) {
|
|
2438
2497
|
if (deps.which)
|
|
2439
2498
|
return deps.which(command);
|
|
@@ -2474,7 +2533,7 @@ function probeCommandVersion(command, args = [], deps = {}, platform = process.p
|
|
|
2474
2533
|
}
|
|
2475
2534
|
}
|
|
2476
2535
|
function resolveHomePath(relativePath, deps = {}) {
|
|
2477
|
-
return
|
|
2536
|
+
return path6.join(deps.homeDir || process.env.HOME || ".", relativePath);
|
|
2478
2537
|
}
|
|
2479
2538
|
function resolveSpawnSpec(command, args, deps = {}, platform = process.platform) {
|
|
2480
2539
|
const resolved = resolveCommandOnPath(command, deps) ?? command;
|
|
@@ -2885,7 +2944,7 @@ class CodexEventNormalizer {
|
|
|
2885
2944
|
|
|
2886
2945
|
// src/drivers/codexHome.ts
|
|
2887
2946
|
import * as os2 from "os";
|
|
2888
|
-
import * as
|
|
2947
|
+
import * as path7 from "path";
|
|
2889
2948
|
function readConfiguredCodexHome(env) {
|
|
2890
2949
|
const raw = env.CODEX_HOME;
|
|
2891
2950
|
return typeof raw === "string" && raw.trim().length > 0 ? raw : null;
|
|
@@ -2893,8 +2952,8 @@ function readConfiguredCodexHome(env) {
|
|
|
2893
2952
|
function resolveCodexHomeRootFromEnv(env = process.env, opts = {}) {
|
|
2894
2953
|
const raw = readConfiguredCodexHome(env);
|
|
2895
2954
|
if (raw)
|
|
2896
|
-
return
|
|
2897
|
-
return
|
|
2955
|
+
return path7.resolve(opts.cwd ?? process.cwd(), raw);
|
|
2956
|
+
return path7.join(opts.defaultHomeDir ?? os2.homedir(), ".codex");
|
|
2898
2957
|
}
|
|
2899
2958
|
|
|
2900
2959
|
// src/drivers/codex.ts
|
|
@@ -3557,8 +3616,8 @@ class KimiDriver {
|
|
|
3557
3616
|
|
|
3558
3617
|
// src/drivers/pi.ts
|
|
3559
3618
|
import { createRequire as createRequire2 } from "module";
|
|
3560
|
-
import { mkdirSync as
|
|
3561
|
-
import * as
|
|
3619
|
+
import { mkdirSync as mkdirSync7, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "fs";
|
|
3620
|
+
import * as path8 from "path";
|
|
3562
3621
|
|
|
3563
3622
|
// src/runtime/sdkRuntimeSession.ts
|
|
3564
3623
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
@@ -3651,15 +3710,15 @@ function resolvePiSdkPackageDir(deps = {}) {
|
|
|
3651
3710
|
if (!binPath)
|
|
3652
3711
|
return;
|
|
3653
3712
|
try {
|
|
3654
|
-
let dir =
|
|
3713
|
+
let dir = path8.dirname(realpathSync(binPath));
|
|
3655
3714
|
const MAX_DEPTH = 8;
|
|
3656
3715
|
for (let i = 0;i < MAX_DEPTH; i++) {
|
|
3657
|
-
if (isPiSdkPackageJson(
|
|
3716
|
+
if (isPiSdkPackageJson(path8.join(dir, "package.json")))
|
|
3658
3717
|
return dir;
|
|
3659
|
-
const siblingDir =
|
|
3660
|
-
if (isPiSdkPackageJson(
|
|
3718
|
+
const siblingDir = path8.join(dir, "node_modules", PI_SDK_PACKAGE_NAME);
|
|
3719
|
+
if (isPiSdkPackageJson(path8.join(siblingDir, "package.json")))
|
|
3661
3720
|
return siblingDir;
|
|
3662
|
-
const parent =
|
|
3721
|
+
const parent = path8.dirname(dir);
|
|
3663
3722
|
if (parent === dir)
|
|
3664
3723
|
break;
|
|
3665
3724
|
dir = parent;
|
|
@@ -3672,7 +3731,7 @@ function resolvePiSdkVersionFromPath(deps = {}) {
|
|
|
3672
3731
|
if (!dir)
|
|
3673
3732
|
return;
|
|
3674
3733
|
try {
|
|
3675
|
-
const pkg = JSON.parse(readFileSync5(
|
|
3734
|
+
const pkg = JSON.parse(readFileSync5(path8.join(dir, "package.json"), "utf-8"));
|
|
3676
3735
|
return pkg.version;
|
|
3677
3736
|
} catch {
|
|
3678
3737
|
return;
|
|
@@ -3745,7 +3804,7 @@ class PiDriver {
|
|
|
3745
3804
|
async createSession(ctx, deps) {
|
|
3746
3805
|
const spawnEnv = await deps.buildSpawnEnv();
|
|
3747
3806
|
if (ctx.standingPrompt) {
|
|
3748
|
-
|
|
3807
|
+
mkdirSync7(ctx.workingDirectory, { recursive: true });
|
|
3749
3808
|
writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
|
|
3750
3809
|
}
|
|
3751
3810
|
const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
@@ -3811,22 +3870,22 @@ function listRuntimeIds() {
|
|
|
3811
3870
|
|
|
3812
3871
|
// src/discovery.ts
|
|
3813
3872
|
function resolveAlookCliPath(moduleDir) {
|
|
3814
|
-
const thisDir = moduleDir ??
|
|
3815
|
-
const target =
|
|
3873
|
+
const thisDir = moduleDir ?? path9.dirname(fileURLToPath(import.meta.url));
|
|
3874
|
+
const target = path9.basename(thisDir) === "dist" ? path9.resolve(thisDir, "cli", "index.js") : path9.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
|
|
3816
3875
|
return fs8.existsSync(target) ? target : null;
|
|
3817
3876
|
}
|
|
3818
3877
|
function deriveCliFallbackCandidates(cliPath) {
|
|
3819
3878
|
if (!cliPath)
|
|
3820
3879
|
return [];
|
|
3821
|
-
const normalized = cliPath.split(
|
|
3880
|
+
const normalized = cliPath.split(path9.sep).join("/");
|
|
3822
3881
|
const marker = "/node_modules/";
|
|
3823
3882
|
const idx = normalized.indexOf(marker);
|
|
3824
3883
|
if (idx === -1)
|
|
3825
3884
|
return [];
|
|
3826
3885
|
const globalRoot = cliPath.slice(0, idx + marker.length - 1);
|
|
3827
|
-
const tail =
|
|
3886
|
+
const tail = path9.join("dist", "cli", "index.js");
|
|
3828
3887
|
return [
|
|
3829
|
-
|
|
3888
|
+
path9.join(globalRoot, "@alook", "daemon", tail)
|
|
3830
3889
|
].filter((candidate) => candidate !== cliPath);
|
|
3831
3890
|
}
|
|
3832
3891
|
function resolveAlookCliPathWithFallback(primary) {
|
|
@@ -3872,7 +3931,7 @@ async function detectRuntimes() {
|
|
|
3872
3931
|
|
|
3873
3932
|
// src/drivers/piSdkDeps.ts
|
|
3874
3933
|
import { readFileSync as readFileSync6 } from "fs";
|
|
3875
|
-
import * as
|
|
3934
|
+
import * as path10 from "path";
|
|
3876
3935
|
import { pathToFileURL } from "url";
|
|
3877
3936
|
var PI_SDK_PACKAGE_NAME2 = "@earendil-works/pi-coding-agent";
|
|
3878
3937
|
var cachedSdkPromise = null;
|
|
@@ -3881,9 +3940,9 @@ async function importPiSdkFromGlobalInstall() {
|
|
|
3881
3940
|
if (!dir) {
|
|
3882
3941
|
throw new Error(`${PI_SDK_PACKAGE_NAME2} not found — install it (e.g. \`npm install -g ${PI_SDK_PACKAGE_NAME2}\`) before launching a pi agent`);
|
|
3883
3942
|
}
|
|
3884
|
-
const pkg = JSON.parse(readFileSync6(
|
|
3943
|
+
const pkg = JSON.parse(readFileSync6(path10.join(dir, "package.json"), "utf-8"));
|
|
3885
3944
|
const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "./dist/index.js";
|
|
3886
|
-
const entryPath =
|
|
3945
|
+
const entryPath = path10.join(dir, entry);
|
|
3887
3946
|
return import(pathToFileURL(entryPath).href);
|
|
3888
3947
|
}
|
|
3889
3948
|
function loadPiSdkModule() {
|
|
@@ -4237,10 +4296,10 @@ function readDaemonVersion() {
|
|
|
4237
4296
|
return "";
|
|
4238
4297
|
}
|
|
4239
4298
|
}
|
|
4240
|
-
var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
|
|
4299
|
+
var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge", "attach"];
|
|
4241
4300
|
function resolveDefaultBaseDir() {
|
|
4242
|
-
const root = process.env.ALOOK_PROJECT_ROOT ||
|
|
4243
|
-
return
|
|
4301
|
+
const root = process.env.ALOOK_PROJECT_ROOT || path11.join(homedir3(), ".alook");
|
|
4302
|
+
return path11.join(root, "daemon");
|
|
4244
4303
|
}
|
|
4245
4304
|
var DEFAULT_BASE_DIR = resolveDefaultBaseDir();
|
|
4246
4305
|
var log = createLogger({ header: "@alook/daemon" });
|
|
@@ -4248,10 +4307,10 @@ function keyHash(machineKey) {
|
|
|
4248
4307
|
return crypto2.createHash("sha256").update(machineKey).digest("hex").slice(0, 12);
|
|
4249
4308
|
}
|
|
4250
4309
|
function daemonsDir(baseDir) {
|
|
4251
|
-
return
|
|
4310
|
+
return path11.join(baseDir, "daemons");
|
|
4252
4311
|
}
|
|
4253
4312
|
function pidfilePath(baseDir, machineKey) {
|
|
4254
|
-
return
|
|
4313
|
+
return path11.join(daemonsDir(baseDir), `${keyHash(machineKey)}.pid`);
|
|
4255
4314
|
}
|
|
4256
4315
|
function isProcessAlive(pid) {
|
|
4257
4316
|
try {
|
|
@@ -4272,7 +4331,7 @@ function readPidFile(filePath) {
|
|
|
4272
4331
|
return null;
|
|
4273
4332
|
}
|
|
4274
4333
|
function writePidFile(filePath, pid, machineKey) {
|
|
4275
|
-
fs9.mkdirSync(
|
|
4334
|
+
fs9.mkdirSync(path11.dirname(filePath), { recursive: true });
|
|
4276
4335
|
fs9.writeFileSync(filePath, JSON.stringify({ pid, key: machineKey }));
|
|
4277
4336
|
}
|
|
4278
4337
|
function acquireLock2(baseDir, machineKey) {
|
|
@@ -4301,7 +4360,7 @@ function daemonList(opts) {
|
|
|
4301
4360
|
const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".pid"));
|
|
4302
4361
|
const results = [];
|
|
4303
4362
|
for (const file of files) {
|
|
4304
|
-
const filePath =
|
|
4363
|
+
const filePath = path11.join(dir, file);
|
|
4305
4364
|
const data = readPidFile(filePath);
|
|
4306
4365
|
if (!data)
|
|
4307
4366
|
continue;
|
|
@@ -4353,7 +4412,7 @@ function daemonStop(opts) {
|
|
|
4353
4412
|
} catch {}
|
|
4354
4413
|
}
|
|
4355
4414
|
function credentialFilePathByMachineId(baseDir, machineId) {
|
|
4356
|
-
return
|
|
4415
|
+
return path11.join(daemonsDir(baseDir), `${machineId}.credential.json`);
|
|
4357
4416
|
}
|
|
4358
4417
|
function credentialFilesDir(baseDir) {
|
|
4359
4418
|
return daemonsDir(baseDir);
|
|
@@ -4370,7 +4429,7 @@ function readCredentialFile(filePath) {
|
|
|
4370
4429
|
return null;
|
|
4371
4430
|
}
|
|
4372
4431
|
function writeCredentialFile(filePath, credential, machineId) {
|
|
4373
|
-
fs9.mkdirSync(
|
|
4432
|
+
fs9.mkdirSync(path11.dirname(filePath), { recursive: true });
|
|
4374
4433
|
fs9.writeFileSync(filePath, JSON.stringify({ credential, machineId }), { mode: 384 });
|
|
4375
4434
|
}
|
|
4376
4435
|
function findExistingCredentialForBearer(baseDir, bearer) {
|
|
@@ -4380,7 +4439,7 @@ function findExistingCredentialForBearer(baseDir, bearer) {
|
|
|
4380
4439
|
for (const file of fs9.readdirSync(dir)) {
|
|
4381
4440
|
if (!file.endsWith(".credential.json"))
|
|
4382
4441
|
continue;
|
|
4383
|
-
const parsed = readCredentialFile(
|
|
4442
|
+
const parsed = readCredentialFile(path11.join(dir, file));
|
|
4384
4443
|
if (parsed && parsed.credential === bearer)
|
|
4385
4444
|
return parsed;
|
|
4386
4445
|
}
|
|
@@ -4549,6 +4608,50 @@ function agentId(opts) {
|
|
|
4549
4608
|
throw new CliError("agent identity required — pass --agent <id> or set ALOOK_AGENT_ID");
|
|
4550
4609
|
return id;
|
|
4551
4610
|
}
|
|
4611
|
+
var CLIENT_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
4612
|
+
var CLIENT_ALLOWED_MIME_PREFIXES = [
|
|
4613
|
+
"image/",
|
|
4614
|
+
"video/",
|
|
4615
|
+
"audio/",
|
|
4616
|
+
"text/",
|
|
4617
|
+
"application/pdf",
|
|
4618
|
+
"application/json",
|
|
4619
|
+
"application/zip",
|
|
4620
|
+
"application/octet-stream"
|
|
4621
|
+
];
|
|
4622
|
+
function mimeAllowed(contentType) {
|
|
4623
|
+
if (!contentType)
|
|
4624
|
+
return false;
|
|
4625
|
+
return CLIENT_ALLOWED_MIME_PREFIXES.some((entry) => entry.endsWith("/") ? contentType.startsWith(entry) : contentType === entry);
|
|
4626
|
+
}
|
|
4627
|
+
function contentTypeFromFilename(filename) {
|
|
4628
|
+
const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
|
|
4629
|
+
switch (ext) {
|
|
4630
|
+
case "png":
|
|
4631
|
+
return "image/png";
|
|
4632
|
+
case "jpg":
|
|
4633
|
+
case "jpeg":
|
|
4634
|
+
return "image/jpeg";
|
|
4635
|
+
case "gif":
|
|
4636
|
+
return "image/gif";
|
|
4637
|
+
case "webp":
|
|
4638
|
+
return "image/webp";
|
|
4639
|
+
case "svg":
|
|
4640
|
+
return "image/svg+xml";
|
|
4641
|
+
case "pdf":
|
|
4642
|
+
return "application/pdf";
|
|
4643
|
+
case "txt":
|
|
4644
|
+
case "md":
|
|
4645
|
+
case "log":
|
|
4646
|
+
return "text/plain";
|
|
4647
|
+
case "json":
|
|
4648
|
+
return "application/json";
|
|
4649
|
+
case "zip":
|
|
4650
|
+
return "application/zip";
|
|
4651
|
+
default:
|
|
4652
|
+
return "application/octet-stream";
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4552
4655
|
async function cmdMessageSend(opts) {
|
|
4553
4656
|
const api = getApi();
|
|
4554
4657
|
const agent = agentId(opts);
|
|
@@ -4566,15 +4669,81 @@ async function cmdMessageSend(opts) {
|
|
|
4566
4669
|
} else if (typeof textFlag === "string") {
|
|
4567
4670
|
text = textFlag;
|
|
4568
4671
|
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4672
|
+
const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
|
|
4673
|
+
const hasText = typeof text === "string" && text.trim().length > 0;
|
|
4674
|
+
if (!hasText && attachmentIds.length === 0) {
|
|
4675
|
+
throw new CliError("message send: --text <text>, --file <path>, or --attachment <id> is required");
|
|
4571
4676
|
}
|
|
4572
|
-
const res = await api.send({
|
|
4677
|
+
const res = await api.send({
|
|
4678
|
+
agentId: agent,
|
|
4679
|
+
channel,
|
|
4680
|
+
content: { text: text ?? "" },
|
|
4681
|
+
attachments: attachmentIds.length > 0 ? attachmentIds : undefined
|
|
4682
|
+
});
|
|
4573
4683
|
if (res.state === "blocked") {
|
|
4574
4684
|
throw new CliError(`channel not aligned: ${res.unreadCount} unread message(s) in ${channel} (latest #${res.latestSeq}). Run \`alook inbox pull\` to align, then resend.`);
|
|
4575
4685
|
}
|
|
4576
4686
|
return { sent: `${res.message.channel}${res.message.seq}` };
|
|
4577
4687
|
}
|
|
4688
|
+
async function cmdAttachmentUpload(opts) {
|
|
4689
|
+
const api = getApi();
|
|
4690
|
+
const agent = agentId(opts);
|
|
4691
|
+
const target = opts.target;
|
|
4692
|
+
const filePath = opts.file;
|
|
4693
|
+
if (!target)
|
|
4694
|
+
throw new CliError("message attachment upload: --target <ref> is required");
|
|
4695
|
+
if (!filePath)
|
|
4696
|
+
throw new CliError("message attachment upload: --file <path> is required");
|
|
4697
|
+
const fs10 = await import("fs/promises");
|
|
4698
|
+
let bytes;
|
|
4699
|
+
try {
|
|
4700
|
+
bytes = await fs10.readFile(filePath);
|
|
4701
|
+
} catch (err) {
|
|
4702
|
+
throw new CliError(`message attachment upload: cannot read file: ${err.message}`);
|
|
4703
|
+
}
|
|
4704
|
+
if (bytes.byteLength > CLIENT_MAX_ATTACHMENT_BYTES) {
|
|
4705
|
+
throw new CliError(`message attachment upload: file too large — ${bytes.byteLength} bytes, max ${CLIENT_MAX_ATTACHMENT_BYTES}`);
|
|
4706
|
+
}
|
|
4707
|
+
const pathMod = await import("path");
|
|
4708
|
+
const filename = pathMod.basename(filePath);
|
|
4709
|
+
const contentType = contentTypeFromFilename(filename);
|
|
4710
|
+
if (!mimeAllowed(contentType)) {
|
|
4711
|
+
throw new CliError(`message attachment upload: content type not allowed: ${contentType}`);
|
|
4712
|
+
}
|
|
4713
|
+
const result = await api.attachmentUpload({
|
|
4714
|
+
agentId: agent,
|
|
4715
|
+
target,
|
|
4716
|
+
file: { data: new Uint8Array(bytes), filename, contentType }
|
|
4717
|
+
});
|
|
4718
|
+
return result;
|
|
4719
|
+
}
|
|
4720
|
+
async function cmdAttachmentDownload(opts) {
|
|
4721
|
+
const api = getApi();
|
|
4722
|
+
const agent = agentId(opts);
|
|
4723
|
+
const id = opts.id;
|
|
4724
|
+
if (!id)
|
|
4725
|
+
throw new CliError("message attachment download: --id <id> is required");
|
|
4726
|
+
const outFlag = opts.out;
|
|
4727
|
+
const os4 = await import("os");
|
|
4728
|
+
const pathMod = await import("path");
|
|
4729
|
+
const destPath = outFlag ?? pathMod.join(os4.tmpdir(), "alook-attachments", agent, id, "file");
|
|
4730
|
+
const result = await api.attachmentDownload({ agentId: agent, id, destPath });
|
|
4731
|
+
if (!outFlag) {
|
|
4732
|
+
const fs10 = await import("fs/promises");
|
|
4733
|
+
const destDir = pathMod.dirname(destPath);
|
|
4734
|
+
const safeName = pathMod.basename(result.filename) || "file";
|
|
4735
|
+
const renamed = pathMod.join(destDir, safeName);
|
|
4736
|
+
if (renamed !== destPath) {
|
|
4737
|
+
try {
|
|
4738
|
+
await fs10.rename(destPath, renamed);
|
|
4739
|
+
return { ...result, path: renamed };
|
|
4740
|
+
} catch {
|
|
4741
|
+
return { ...result, path: destPath };
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
return result;
|
|
4746
|
+
}
|
|
4578
4747
|
async function cmdInboxPull(opts) {
|
|
4579
4748
|
const api = getApi();
|
|
4580
4749
|
const agent = agentId(opts);
|
|
@@ -4654,12 +4823,26 @@ function buildProgram() {
|
|
|
4654
4823
|
}).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
|
|
4655
4824
|
const message = program.command("message").description("message operations").exitOverride();
|
|
4656
4825
|
message.configureOutput({ writeOut: () => {}, writeErr: () => {} });
|
|
4657
|
-
message.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace/general)").option("--text <text>", "inline message body (short messages)").option("--file <path>", "read message body from a file (long messages)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
4826
|
+
message.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace/general)").option("--text <text>", "inline message body (short messages)").option("--file <path>", "read message body from a file (long messages)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
4658
4827
|
const localOpts = this.opts();
|
|
4659
4828
|
const globalOpts = program.opts();
|
|
4660
4829
|
const result = await cmdMessageSend({ ...globalOpts, ...localOpts });
|
|
4661
4830
|
printEnvelope({ success: result });
|
|
4662
4831
|
});
|
|
4832
|
+
const attachment = message.command("attachment").description("attachment operations").exitOverride();
|
|
4833
|
+
attachment.configureOutput({ writeOut: () => {}, writeErr: () => {} });
|
|
4834
|
+
attachment.command("upload").description("upload a local file as a pending attachment for a future send").option("--target <ref>", "destination (channel, DM, or thread ref)").option("--file <path>", "local file to upload").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
4835
|
+
const localOpts = this.opts();
|
|
4836
|
+
const globalOpts = program.opts();
|
|
4837
|
+
const result = await cmdAttachmentUpload({ ...globalOpts, ...localOpts });
|
|
4838
|
+
printEnvelope({ success: result });
|
|
4839
|
+
});
|
|
4840
|
+
attachment.command("download").description("download an attachment by id to disk").option("--id <id>", "attachment id (from inbox pull / send response)").option("--out <path>", "explicit output path (default: /tmp/alook-attachments/<agent>/<id>/<filename>)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
|
4841
|
+
const localOpts = this.opts();
|
|
4842
|
+
const globalOpts = program.opts();
|
|
4843
|
+
const result = await cmdAttachmentDownload({ ...globalOpts, ...localOpts });
|
|
4844
|
+
printEnvelope({ success: result });
|
|
4845
|
+
});
|
|
4663
4846
|
const inbox = program.command("inbox").description("inbox operations").exitOverride();
|
|
4664
4847
|
inbox.configureOutput({ writeOut: () => {}, writeErr: () => {} });
|
|
4665
4848
|
inbox.command("pull").description("fetch unread messages from all channels").option("--max <n>", "max messages to return").option("--no-ack", "do not advance read waterlines (peek only)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,9 @@ function cliCommandsSection() {
|
|
|
31
31
|
"### Messaging",
|
|
32
32
|
"",
|
|
33
33
|
`1. \`${CLI} inbox pull\` — fetch unread messages.`,
|
|
34
|
-
`2. \`${CLI} message send\` — send a message to a channel, DM, or thread.`,
|
|
34
|
+
`2. \`${CLI} message send\` — send a message to a channel, DM, or thread. ` + `Attach files with \`--attachment <id>\` (repeatable, order matters).`,
|
|
35
|
+
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a local file; ` + `returns an id. Feed that id into \`message send --attachment <id>\`. ` + `The id is stable across the pending→persisted lifecycle.`,
|
|
36
|
+
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download an attachment ` + `id from any message you have access to (or your own pending uploads).`,
|
|
35
37
|
"",
|
|
36
38
|
"### Servers",
|
|
37
39
|
"",
|
|
@@ -4093,6 +4095,8 @@ function parseBearer(authHeader) {
|
|
|
4093
4095
|
return m ? m[1].trim() : null;
|
|
4094
4096
|
}
|
|
4095
4097
|
var DEFAULT_CAPABILITY_RESOLVER = (_method, pathname) => {
|
|
4098
|
+
if (pathname.includes("/attachment"))
|
|
4099
|
+
return "attach";
|
|
4096
4100
|
if (pathname.includes("/send"))
|
|
4097
4101
|
return "send";
|
|
4098
4102
|
if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
|
|
@@ -4120,7 +4124,7 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
4120
4124
|
return;
|
|
4121
4125
|
}
|
|
4122
4126
|
const reg = verdict.reg;
|
|
4123
|
-
const isInboxPull = onPull && pathname
|
|
4127
|
+
const isInboxPull = onPull && pathname === "/api/inboxPull";
|
|
4124
4128
|
if (onProxyRequest) {
|
|
4125
4129
|
try {
|
|
4126
4130
|
onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alook/daemon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.155",
|
|
4
4
|
"description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/alookai/alook#readme",
|