@alook/cli 0.0.9 → 0.0.11
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 +215 -15
- package/dist/session-runner.js +158 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
|
-
import { Command as
|
|
18
|
+
import { Command as Command10 } from "commander";
|
|
19
19
|
|
|
20
20
|
// commands/register.ts
|
|
21
21
|
import { Command } from "commander";
|
|
@@ -13896,7 +13896,8 @@ var TaskApiBaseSchema = exports_external.object({
|
|
|
13896
13896
|
error: exports_external.string().nullable(),
|
|
13897
13897
|
created_at: exports_external.string(),
|
|
13898
13898
|
type: exports_external.string(),
|
|
13899
|
-
context_key: exports_external.string().nullable().optional()
|
|
13899
|
+
context_key: exports_external.string().nullable().optional(),
|
|
13900
|
+
context: exports_external.unknown().nullable().optional()
|
|
13900
13901
|
});
|
|
13901
13902
|
var TaskApiSchema = TaskApiBaseSchema.extend({
|
|
13902
13903
|
agent: TaskAgentDataApiSchema.nullable().optional()
|
|
@@ -14015,6 +14016,64 @@ var CalendarEventApiSchema = exports_external.object({
|
|
|
14015
14016
|
var AddWhitelistRequestSchema = exports_external.object({
|
|
14016
14017
|
email: exports_external.string().email()
|
|
14017
14018
|
});
|
|
14019
|
+
var RuntimeConfigSchema = exports_external.object({ model: exports_external.string().max(100).optional() }).passthrough().optional();
|
|
14020
|
+
var CreateAgentRequestSchema = exports_external.object({
|
|
14021
|
+
name: exports_external.string().min(1, "name is required"),
|
|
14022
|
+
description: exports_external.string().optional().default(""),
|
|
14023
|
+
instructions: exports_external.string().optional().default(""),
|
|
14024
|
+
runtime_id: exports_external.string().min(1, "runtime_id is required"),
|
|
14025
|
+
runtime_config: RuntimeConfigSchema,
|
|
14026
|
+
max_concurrent_tasks: exports_external.number().int().optional(),
|
|
14027
|
+
email_handle: exports_external.string().optional()
|
|
14028
|
+
});
|
|
14029
|
+
var UpdateAgentRequestSchema = exports_external.object({
|
|
14030
|
+
name: exports_external.string().min(1).optional(),
|
|
14031
|
+
description: exports_external.string().optional(),
|
|
14032
|
+
instructions: exports_external.string().optional(),
|
|
14033
|
+
runtime_id: exports_external.string().min(1).optional(),
|
|
14034
|
+
runtime_config: RuntimeConfigSchema
|
|
14035
|
+
}).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined, { message: "at least one field is required" });
|
|
14036
|
+
var CreateConversationRequestSchema = exports_external.object({
|
|
14037
|
+
agent_id: exports_external.string().min(1, "agent_id is required")
|
|
14038
|
+
});
|
|
14039
|
+
var CreateMessageRequestSchema = exports_external.object({
|
|
14040
|
+
content: exports_external.string().min(1, "content is required")
|
|
14041
|
+
});
|
|
14042
|
+
var EmailAttachmentSchema = exports_external.object({
|
|
14043
|
+
key: exports_external.string().min(1),
|
|
14044
|
+
filename: exports_external.string().min(1),
|
|
14045
|
+
size: exports_external.number().int().nonnegative().optional(),
|
|
14046
|
+
contentType: exports_external.string().min(1)
|
|
14047
|
+
});
|
|
14048
|
+
var SendEmailRequestSchema = exports_external.object({
|
|
14049
|
+
agentId: exports_external.string().min(1, "agentId is required"),
|
|
14050
|
+
to: exports_external.string().min(1, "to is required"),
|
|
14051
|
+
subject: exports_external.string().min(1, "subject is required"),
|
|
14052
|
+
htmlBody: exports_external.string().default(""),
|
|
14053
|
+
inReplyTo: exports_external.string().optional(),
|
|
14054
|
+
references: exports_external.string().optional(),
|
|
14055
|
+
attachments: exports_external.array(EmailAttachmentSchema).optional()
|
|
14056
|
+
});
|
|
14057
|
+
var UpdateEmailStatusRequestSchema = exports_external.object({
|
|
14058
|
+
status: exports_external.enum(["unread", "read", "archived"])
|
|
14059
|
+
});
|
|
14060
|
+
var EmailNotifyRequestSchema = exports_external.object({
|
|
14061
|
+
agentId: exports_external.string().min(1),
|
|
14062
|
+
workspaceId: exports_external.string().min(1),
|
|
14063
|
+
r2Key: exports_external.string().min(1),
|
|
14064
|
+
from: exports_external.string().min(1),
|
|
14065
|
+
to: exports_external.string().optional(),
|
|
14066
|
+
subject: exports_external.string().min(1),
|
|
14067
|
+
isWhitelisted: exports_external.boolean(),
|
|
14068
|
+
forwarded: exports_external.boolean().optional().default(false),
|
|
14069
|
+
messageId: exports_external.string().optional().default(""),
|
|
14070
|
+
inReplyTo: exports_external.string().optional().default(""),
|
|
14071
|
+
references: exports_external.string().optional().default("")
|
|
14072
|
+
});
|
|
14073
|
+
var CreateWorkspaceRequestSchema = exports_external.object({
|
|
14074
|
+
name: exports_external.string().min(1, "name is required"),
|
|
14075
|
+
slug: exports_external.string().min(1, "slug is required")
|
|
14076
|
+
});
|
|
14018
14077
|
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
|
|
14019
14078
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
14020
14079
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15521,6 +15580,7 @@ var message = sqliteTable("message", {
|
|
|
15521
15580
|
role: text("role").notNull(),
|
|
15522
15581
|
content: text("content").notNull().default(""),
|
|
15523
15582
|
taskId: text("task_id"),
|
|
15583
|
+
attachmentIds: text("attachment_ids"),
|
|
15524
15584
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15525
15585
|
});
|
|
15526
15586
|
var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
@@ -15605,6 +15665,24 @@ var calendarEvent = sqliteTable("calendar_event", {
|
|
|
15605
15665
|
foreignColumns: [agent.id, agent.workspaceId]
|
|
15606
15666
|
}).onDelete("cascade")
|
|
15607
15667
|
]);
|
|
15668
|
+
var artifact = sqliteTable("artifact", {
|
|
15669
|
+
id: text("id").primaryKey().$defaultFn(() => "art_" + nanoid3()),
|
|
15670
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15671
|
+
agentId: text("agent_id").notNull(),
|
|
15672
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15673
|
+
filename: text("filename").notNull(),
|
|
15674
|
+
contentType: text("content_type").notNull().default("application/octet-stream"),
|
|
15675
|
+
size: integer2("size").notNull(),
|
|
15676
|
+
r2Key: text("r2_key").notNull(),
|
|
15677
|
+
source: text("source").notNull().default("agent"),
|
|
15678
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15679
|
+
}, (t) => [
|
|
15680
|
+
index("idx_artifact_conversation").on(t.conversationId),
|
|
15681
|
+
foreignKey({
|
|
15682
|
+
columns: [t.agentId, t.workspaceId],
|
|
15683
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15684
|
+
}).onDelete("cascade")
|
|
15685
|
+
]);
|
|
15608
15686
|
var machineToken = sqliteTable("machine_token", {
|
|
15609
15687
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15610
15688
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
@@ -15701,6 +15779,16 @@ class DaemonClient {
|
|
|
15701
15779
|
error: error48
|
|
15702
15780
|
});
|
|
15703
15781
|
}
|
|
15782
|
+
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
15783
|
+
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
15784
|
+
}
|
|
15785
|
+
async downloadArtifact(token, artifactId, workspaceId) {
|
|
15786
|
+
const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
15787
|
+
if (!res.ok) {
|
|
15788
|
+
throw new Error(`artifact download failed: HTTP ${res.status}`);
|
|
15789
|
+
}
|
|
15790
|
+
return res.arrayBuffer();
|
|
15791
|
+
}
|
|
15704
15792
|
reportMessages(token, taskId, messages) {
|
|
15705
15793
|
return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
|
|
15706
15794
|
}
|
|
@@ -15735,6 +15823,10 @@ function pidFilePath(profile) {
|
|
|
15735
15823
|
const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
|
|
15736
15824
|
return join3(configDir(), name);
|
|
15737
15825
|
}
|
|
15826
|
+
function lastUpdateMarkerPath(profile) {
|
|
15827
|
+
const name = profile ? `last_update_${profile}` : "last_update";
|
|
15828
|
+
return join3(configDir(), name);
|
|
15829
|
+
}
|
|
15738
15830
|
function daemonLogDir() {
|
|
15739
15831
|
return join3(configDir(), "daemon", "logs");
|
|
15740
15832
|
}
|
|
@@ -15871,6 +15963,7 @@ function fromApiTask(api2) {
|
|
|
15871
15963
|
priority: api2.priority,
|
|
15872
15964
|
type: api2.type,
|
|
15873
15965
|
contextKey: api2.context_key ?? null,
|
|
15966
|
+
context: api2.context ?? undefined,
|
|
15874
15967
|
agent: api2.agent ? { name: api2.agent.name, instructions: api2.agent.instructions, emailHandle: api2.agent.email_handle ?? undefined, userEmail: api2.agent.user_email ?? undefined, runtimeConfig: api2.agent.runtime_config ?? undefined } : undefined,
|
|
15875
15968
|
repos: undefined,
|
|
15876
15969
|
createdAt: api2.created_at
|
|
@@ -16015,6 +16108,9 @@ function releaseDaemonPid(profile) {
|
|
|
16015
16108
|
removePidFileIfMatches(process.pid, profile);
|
|
16016
16109
|
}
|
|
16017
16110
|
|
|
16111
|
+
// daemon/update-handler.ts
|
|
16112
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
16113
|
+
|
|
16018
16114
|
// lib/update.ts
|
|
16019
16115
|
import { spawn } from "child_process";
|
|
16020
16116
|
function fetchLatestVersion() {
|
|
@@ -16049,16 +16145,39 @@ var MAX_RETRIES = 3;
|
|
|
16049
16145
|
function isUpdating() {
|
|
16050
16146
|
return updating;
|
|
16051
16147
|
}
|
|
16052
|
-
|
|
16148
|
+
function readUpdateMarker(profile) {
|
|
16149
|
+
try {
|
|
16150
|
+
return readFileSync4(lastUpdateMarkerPath(profile), "utf-8").trim() || null;
|
|
16151
|
+
} catch {
|
|
16152
|
+
return null;
|
|
16153
|
+
}
|
|
16154
|
+
}
|
|
16155
|
+
function writeUpdateMarker(version3, profile) {
|
|
16156
|
+
try {
|
|
16157
|
+
writeFileSync3(lastUpdateMarkerPath(profile), version3, { mode: 384 });
|
|
16158
|
+
} catch {}
|
|
16159
|
+
}
|
|
16160
|
+
function clearUpdateMarker(profile) {
|
|
16161
|
+
try {
|
|
16162
|
+
unlinkSync2(lastUpdateMarkerPath(profile));
|
|
16163
|
+
} catch {}
|
|
16164
|
+
}
|
|
16165
|
+
async function handleCliUpdate(version3, onSuccess, profile) {
|
|
16053
16166
|
if (updating)
|
|
16054
16167
|
return;
|
|
16055
16168
|
if (retryCount >= MAX_RETRIES)
|
|
16056
16169
|
return;
|
|
16170
|
+
const marker = readUpdateMarker(profile);
|
|
16171
|
+
if (marker === version3) {
|
|
16172
|
+
log.info(`Skipping update to v${version3} — already attempted (marker exists)`);
|
|
16173
|
+
return;
|
|
16174
|
+
}
|
|
16057
16175
|
updating = true;
|
|
16058
16176
|
try {
|
|
16059
16177
|
log.info(`Updating CLI to v${version3}...`);
|
|
16060
16178
|
const result = await runNpmUpdate(version3);
|
|
16061
16179
|
if (result.success) {
|
|
16180
|
+
writeUpdateMarker(version3, profile);
|
|
16062
16181
|
log.info(`CLI updated to v${version3} — restarting`);
|
|
16063
16182
|
onSuccess();
|
|
16064
16183
|
} else {
|
|
@@ -16074,7 +16193,7 @@ async function handleCliUpdate(version3, onSuccess) {
|
|
|
16074
16193
|
}
|
|
16075
16194
|
|
|
16076
16195
|
// daemon/daemon.ts
|
|
16077
|
-
import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as
|
|
16196
|
+
import { existsSync, mkdirSync as mkdirSync3, openSync, closeSync, renameSync, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
|
|
16078
16197
|
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
16079
16198
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
16080
16199
|
import { dirname as dirname3, join as join4 } from "path";
|
|
@@ -16111,7 +16230,7 @@ function pruneSessionRunnerLogs() {
|
|
|
16111
16230
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16112
16231
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
16113
16232
|
try {
|
|
16114
|
-
|
|
16233
|
+
unlinkSync3(join4(logDir, entry.name));
|
|
16115
16234
|
} catch {}
|
|
16116
16235
|
}
|
|
16117
16236
|
}
|
|
@@ -16131,6 +16250,11 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16131
16250
|
const config2 = loadDaemonConfig(profile);
|
|
16132
16251
|
if (serverUrl)
|
|
16133
16252
|
config2.serverURL = serverUrl;
|
|
16253
|
+
const marker = readUpdateMarker(profile);
|
|
16254
|
+
if (marker && marker === config2.cliVersion) {
|
|
16255
|
+
clearUpdateMarker(profile);
|
|
16256
|
+
log.info(`Cleared update marker — now running v${config2.cliVersion}`);
|
|
16257
|
+
}
|
|
16134
16258
|
const cliConfig = loadCLIConfigForProfile(profile);
|
|
16135
16259
|
const workspaces = cliConfig.watched_workspaces || [];
|
|
16136
16260
|
if (workspaces.length === 0) {
|
|
@@ -16271,8 +16395,8 @@ async function startDaemon(profile, serverUrl) {
|
|
|
16271
16395
|
evictedIds.push(ws.workspaceId);
|
|
16272
16396
|
continue;
|
|
16273
16397
|
}
|
|
16274
|
-
if (pending_update && !isUpdating()) {
|
|
16275
|
-
handleCliUpdate(pending_update.version, () => requestRestart());
|
|
16398
|
+
if (pending_update && !isUpdating() && pending_update.version !== config2.cliVersion) {
|
|
16399
|
+
handleCliUpdate(pending_update.version, () => requestRestart(), profile);
|
|
16276
16400
|
}
|
|
16277
16401
|
for (const apiTask of apiTasks) {
|
|
16278
16402
|
const task = fromApiTask(apiTask);
|
|
@@ -16541,7 +16665,7 @@ function configCommand() {
|
|
|
16541
16665
|
|
|
16542
16666
|
// commands/email.ts
|
|
16543
16667
|
import { Command as Command5 } from "commander";
|
|
16544
|
-
import { writeFileSync as
|
|
16668
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
16545
16669
|
import { basename, join as join5 } from "path";
|
|
16546
16670
|
import PostalMime from "postal-mime";
|
|
16547
16671
|
var VALID_STATUSES = ["unread", "read", "archived"];
|
|
@@ -16632,7 +16756,7 @@ function emailCommand() {
|
|
|
16632
16756
|
references: email3.references || ""
|
|
16633
16757
|
};
|
|
16634
16758
|
const metadataPath = join5(emailDir, "metadata.json");
|
|
16635
|
-
|
|
16759
|
+
writeFileSync4(metadataPath, JSON.stringify(metadata, null, 2));
|
|
16636
16760
|
downloadedPaths.push(metadataPath);
|
|
16637
16761
|
let rawMime;
|
|
16638
16762
|
try {
|
|
@@ -16648,12 +16772,12 @@ function emailCommand() {
|
|
|
16648
16772
|
const parsed = await new PostalMime().parse(rawMime);
|
|
16649
16773
|
if (parsed.text) {
|
|
16650
16774
|
const bodyPath = join5(emailDir, "body.txt");
|
|
16651
|
-
|
|
16775
|
+
writeFileSync4(bodyPath, parsed.text);
|
|
16652
16776
|
downloadedPaths.push(bodyPath);
|
|
16653
16777
|
}
|
|
16654
16778
|
if (parsed.html) {
|
|
16655
16779
|
const htmlPath = join5(emailDir, "body.html");
|
|
16656
|
-
|
|
16780
|
+
writeFileSync4(htmlPath, parsed.html);
|
|
16657
16781
|
downloadedPaths.push(htmlPath);
|
|
16658
16782
|
}
|
|
16659
16783
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
@@ -16677,7 +16801,7 @@ function emailCommand() {
|
|
|
16677
16801
|
} else {
|
|
16678
16802
|
buf = Buffer.from(content);
|
|
16679
16803
|
}
|
|
16680
|
-
|
|
16804
|
+
writeFileSync4(attPath, buf);
|
|
16681
16805
|
downloadedPaths.push(attPath);
|
|
16682
16806
|
}
|
|
16683
16807
|
}
|
|
@@ -16716,7 +16840,7 @@ function emailCommand() {
|
|
|
16716
16840
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
16717
16841
|
let htmlBody;
|
|
16718
16842
|
try {
|
|
16719
|
-
htmlBody =
|
|
16843
|
+
htmlBody = readFileSync5(opts.bodyFile, "utf-8");
|
|
16720
16844
|
} catch (err) {
|
|
16721
16845
|
console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
|
|
16722
16846
|
process.exit(1);
|
|
@@ -16732,7 +16856,7 @@ function emailCommand() {
|
|
|
16732
16856
|
let bytes;
|
|
16733
16857
|
let size;
|
|
16734
16858
|
try {
|
|
16735
|
-
bytes =
|
|
16859
|
+
bytes = readFileSync5(path);
|
|
16736
16860
|
size = statSync2(path).size;
|
|
16737
16861
|
} catch (err) {
|
|
16738
16862
|
console.error(`Error: cannot read attachment "${path}": ${err instanceof Error ? err.message : err}`);
|
|
@@ -17034,8 +17158,83 @@ ${result.output}`);
|
|
|
17034
17158
|
return cmd;
|
|
17035
17159
|
}
|
|
17036
17160
|
|
|
17161
|
+
// commands/sync.ts
|
|
17162
|
+
import { Command as Command9 } from "commander";
|
|
17163
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
17164
|
+
import { basename as basename2 } from "path";
|
|
17165
|
+
var MIME_BY_EXT2 = {
|
|
17166
|
+
".pdf": "application/pdf",
|
|
17167
|
+
".png": "image/png",
|
|
17168
|
+
".jpg": "image/jpeg",
|
|
17169
|
+
".jpeg": "image/jpeg",
|
|
17170
|
+
".gif": "image/gif",
|
|
17171
|
+
".txt": "text/plain",
|
|
17172
|
+
".html": "text/html",
|
|
17173
|
+
".json": "application/json",
|
|
17174
|
+
".csv": "text/csv",
|
|
17175
|
+
".md": "text/markdown",
|
|
17176
|
+
".ts": "text/typescript",
|
|
17177
|
+
".js": "text/javascript",
|
|
17178
|
+
".yaml": "text/yaml",
|
|
17179
|
+
".yml": "text/yaml",
|
|
17180
|
+
".svg": "image/svg+xml",
|
|
17181
|
+
".xml": "application/xml",
|
|
17182
|
+
".zip": "application/zip"
|
|
17183
|
+
};
|
|
17184
|
+
function guessContentType2(filename) {
|
|
17185
|
+
const idx = filename.lastIndexOf(".");
|
|
17186
|
+
if (idx < 0)
|
|
17187
|
+
return "application/octet-stream";
|
|
17188
|
+
const ext = filename.slice(idx).toLowerCase();
|
|
17189
|
+
return MIME_BY_EXT2[ext] ?? "application/octet-stream";
|
|
17190
|
+
}
|
|
17191
|
+
function resolveClientOpts3(command, agentId) {
|
|
17192
|
+
const parentOpts = command.parent?.parent?.opts() || {};
|
|
17193
|
+
const profile = parentOpts.profile;
|
|
17194
|
+
const cfg = loadCLIConfigForProfile(profile);
|
|
17195
|
+
const serverUrl = parentOpts.server || cfg.server_url;
|
|
17196
|
+
const workspaces = cfg.watched_workspaces || [];
|
|
17197
|
+
const ws = workspaces.find((w) => w.agent_ids?.includes(agentId));
|
|
17198
|
+
if (!ws || !ws.token) {
|
|
17199
|
+
console.error(`Error: no registered workspace contains agent ${agentId}. Run '${cmdPrefix()} register --token <token>' first.`);
|
|
17200
|
+
process.exit(1);
|
|
17201
|
+
}
|
|
17202
|
+
return { serverUrl, token: ws.token, workspaceId: ws.id };
|
|
17203
|
+
}
|
|
17204
|
+
function syncCommand() {
|
|
17205
|
+
const cmd = new Command9("sync").description("File sync utilities");
|
|
17206
|
+
cmd.command("upload-artifact").description("Upload a file artifact to a conversation").requiredOption("--agent_id <id>", "Agent ID").requiredOption("--conversation_id <id>", "Conversation ID").requiredOption("--file <path>", "Path to file to upload").action(async (opts, command) => {
|
|
17207
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, opts.agent_id);
|
|
17208
|
+
const client = new APIClient(serverUrl, token, workspaceId);
|
|
17209
|
+
let bytes;
|
|
17210
|
+
let size;
|
|
17211
|
+
try {
|
|
17212
|
+
const stat = statSync3(opts.file);
|
|
17213
|
+
size = stat.size;
|
|
17214
|
+
bytes = readFileSync6(opts.file);
|
|
17215
|
+
} catch (err) {
|
|
17216
|
+
console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
|
|
17217
|
+
process.exit(1);
|
|
17218
|
+
}
|
|
17219
|
+
const filename = basename2(opts.file);
|
|
17220
|
+
const contentType = guessContentType2(filename);
|
|
17221
|
+
const form = new FormData;
|
|
17222
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
17223
|
+
form.append("agent_id", opts.agent_id);
|
|
17224
|
+
form.append("conversation_id", opts.conversation_id);
|
|
17225
|
+
try {
|
|
17226
|
+
const result = await client.postMultipart("/api/artifacts/upload", form);
|
|
17227
|
+
printJSON(result);
|
|
17228
|
+
} catch (err) {
|
|
17229
|
+
console.error(`Error uploading artifact: ${err.message}`);
|
|
17230
|
+
process.exit(1);
|
|
17231
|
+
}
|
|
17232
|
+
});
|
|
17233
|
+
return cmd;
|
|
17234
|
+
}
|
|
17235
|
+
|
|
17037
17236
|
// src/index.ts
|
|
17038
|
-
var program = new
|
|
17237
|
+
var program = new Command10;
|
|
17039
17238
|
program.name("alook").description("Alook CLI").option("--server <url>", "Server URL").option("--profile <name>", "Profile name");
|
|
17040
17239
|
program.addCommand(registerCommand());
|
|
17041
17240
|
program.addCommand(statusCommand());
|
|
@@ -17045,4 +17244,5 @@ program.addCommand(calendarCommand());
|
|
|
17045
17244
|
program.addCommand(configCommand());
|
|
17046
17245
|
program.addCommand(versionCommand());
|
|
17047
17246
|
program.addCommand(updateCommand());
|
|
17247
|
+
program.addCommand(syncCommand());
|
|
17048
17248
|
program.parse();
|
package/dist/session-runner.js
CHANGED
|
@@ -15,6 +15,8 @@ var __export = (target, all) => {
|
|
|
15
15
|
|
|
16
16
|
// daemon/session-runner.ts
|
|
17
17
|
import { createWriteStream } from "fs";
|
|
18
|
+
import { mkdir, writeFile, rm } from "fs/promises";
|
|
19
|
+
import path from "path";
|
|
18
20
|
|
|
19
21
|
// ../shared/src/constants.ts
|
|
20
22
|
var TASK_TYPES = {
|
|
@@ -13612,7 +13614,8 @@ var TaskApiBaseSchema = exports_external.object({
|
|
|
13612
13614
|
error: exports_external.string().nullable(),
|
|
13613
13615
|
created_at: exports_external.string(),
|
|
13614
13616
|
type: exports_external.string(),
|
|
13615
|
-
context_key: exports_external.string().nullable().optional()
|
|
13617
|
+
context_key: exports_external.string().nullable().optional(),
|
|
13618
|
+
context: exports_external.unknown().nullable().optional()
|
|
13616
13619
|
});
|
|
13617
13620
|
var TaskApiSchema = TaskApiBaseSchema.extend({
|
|
13618
13621
|
agent: TaskAgentDataApiSchema.nullable().optional()
|
|
@@ -13731,6 +13734,64 @@ var CalendarEventApiSchema = exports_external.object({
|
|
|
13731
13734
|
var AddWhitelistRequestSchema = exports_external.object({
|
|
13732
13735
|
email: exports_external.string().email()
|
|
13733
13736
|
});
|
|
13737
|
+
var RuntimeConfigSchema = exports_external.object({ model: exports_external.string().max(100).optional() }).passthrough().optional();
|
|
13738
|
+
var CreateAgentRequestSchema = exports_external.object({
|
|
13739
|
+
name: exports_external.string().min(1, "name is required"),
|
|
13740
|
+
description: exports_external.string().optional().default(""),
|
|
13741
|
+
instructions: exports_external.string().optional().default(""),
|
|
13742
|
+
runtime_id: exports_external.string().min(1, "runtime_id is required"),
|
|
13743
|
+
runtime_config: RuntimeConfigSchema,
|
|
13744
|
+
max_concurrent_tasks: exports_external.number().int().optional(),
|
|
13745
|
+
email_handle: exports_external.string().optional()
|
|
13746
|
+
});
|
|
13747
|
+
var UpdateAgentRequestSchema = exports_external.object({
|
|
13748
|
+
name: exports_external.string().min(1).optional(),
|
|
13749
|
+
description: exports_external.string().optional(),
|
|
13750
|
+
instructions: exports_external.string().optional(),
|
|
13751
|
+
runtime_id: exports_external.string().min(1).optional(),
|
|
13752
|
+
runtime_config: RuntimeConfigSchema
|
|
13753
|
+
}).refine((v) => v.name !== undefined || v.description !== undefined || v.instructions !== undefined || v.runtime_id !== undefined || v.runtime_config !== undefined, { message: "at least one field is required" });
|
|
13754
|
+
var CreateConversationRequestSchema = exports_external.object({
|
|
13755
|
+
agent_id: exports_external.string().min(1, "agent_id is required")
|
|
13756
|
+
});
|
|
13757
|
+
var CreateMessageRequestSchema = exports_external.object({
|
|
13758
|
+
content: exports_external.string().min(1, "content is required")
|
|
13759
|
+
});
|
|
13760
|
+
var EmailAttachmentSchema = exports_external.object({
|
|
13761
|
+
key: exports_external.string().min(1),
|
|
13762
|
+
filename: exports_external.string().min(1),
|
|
13763
|
+
size: exports_external.number().int().nonnegative().optional(),
|
|
13764
|
+
contentType: exports_external.string().min(1)
|
|
13765
|
+
});
|
|
13766
|
+
var SendEmailRequestSchema = exports_external.object({
|
|
13767
|
+
agentId: exports_external.string().min(1, "agentId is required"),
|
|
13768
|
+
to: exports_external.string().min(1, "to is required"),
|
|
13769
|
+
subject: exports_external.string().min(1, "subject is required"),
|
|
13770
|
+
htmlBody: exports_external.string().default(""),
|
|
13771
|
+
inReplyTo: exports_external.string().optional(),
|
|
13772
|
+
references: exports_external.string().optional(),
|
|
13773
|
+
attachments: exports_external.array(EmailAttachmentSchema).optional()
|
|
13774
|
+
});
|
|
13775
|
+
var UpdateEmailStatusRequestSchema = exports_external.object({
|
|
13776
|
+
status: exports_external.enum(["unread", "read", "archived"])
|
|
13777
|
+
});
|
|
13778
|
+
var EmailNotifyRequestSchema = exports_external.object({
|
|
13779
|
+
agentId: exports_external.string().min(1),
|
|
13780
|
+
workspaceId: exports_external.string().min(1),
|
|
13781
|
+
r2Key: exports_external.string().min(1),
|
|
13782
|
+
from: exports_external.string().min(1),
|
|
13783
|
+
to: exports_external.string().optional(),
|
|
13784
|
+
subject: exports_external.string().min(1),
|
|
13785
|
+
isWhitelisted: exports_external.boolean(),
|
|
13786
|
+
forwarded: exports_external.boolean().optional().default(false),
|
|
13787
|
+
messageId: exports_external.string().optional().default(""),
|
|
13788
|
+
inReplyTo: exports_external.string().optional().default(""),
|
|
13789
|
+
references: exports_external.string().optional().default("")
|
|
13790
|
+
});
|
|
13791
|
+
var CreateWorkspaceRequestSchema = exports_external.object({
|
|
13792
|
+
name: exports_external.string().min(1, "name is required"),
|
|
13793
|
+
slug: exports_external.string().min(1, "slug is required")
|
|
13794
|
+
});
|
|
13734
13795
|
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudflare+workers-types@4.20260418.1_@opentelemetry+api@1.9.1_bun-types@1.3.12_kysely@0.28.16/node_modules/drizzle-orm/entity.js
|
|
13735
13796
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
13736
13797
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
@@ -15237,6 +15298,7 @@ var message = sqliteTable("message", {
|
|
|
15237
15298
|
role: text("role").notNull(),
|
|
15238
15299
|
content: text("content").notNull().default(""),
|
|
15239
15300
|
taskId: text("task_id"),
|
|
15301
|
+
attachmentIds: text("attachment_ids"),
|
|
15240
15302
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15241
15303
|
});
|
|
15242
15304
|
var agentTaskQueue = sqliteTable("agent_task_queue", {
|
|
@@ -15321,6 +15383,24 @@ var calendarEvent = sqliteTable("calendar_event", {
|
|
|
15321
15383
|
foreignColumns: [agent.id, agent.workspaceId]
|
|
15322
15384
|
}).onDelete("cascade")
|
|
15323
15385
|
]);
|
|
15386
|
+
var artifact = sqliteTable("artifact", {
|
|
15387
|
+
id: text("id").primaryKey().$defaultFn(() => "art_" + nanoid3()),
|
|
15388
|
+
conversationId: text("conversation_id").notNull().references(() => conversation.id, { onDelete: "cascade" }),
|
|
15389
|
+
agentId: text("agent_id").notNull(),
|
|
15390
|
+
workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
|
|
15391
|
+
filename: text("filename").notNull(),
|
|
15392
|
+
contentType: text("content_type").notNull().default("application/octet-stream"),
|
|
15393
|
+
size: integer2("size").notNull(),
|
|
15394
|
+
r2Key: text("r2_key").notNull(),
|
|
15395
|
+
source: text("source").notNull().default("agent"),
|
|
15396
|
+
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
15397
|
+
}, (t) => [
|
|
15398
|
+
index("idx_artifact_conversation").on(t.conversationId),
|
|
15399
|
+
foreignKey({
|
|
15400
|
+
columns: [t.agentId, t.workspaceId],
|
|
15401
|
+
foreignColumns: [agent.id, agent.workspaceId]
|
|
15402
|
+
}).onDelete("cascade")
|
|
15403
|
+
]);
|
|
15324
15404
|
var machineToken = sqliteTable("machine_token", {
|
|
15325
15405
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
15326
15406
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
@@ -15406,6 +15486,16 @@ class DaemonClient {
|
|
|
15406
15486
|
error: error48
|
|
15407
15487
|
});
|
|
15408
15488
|
}
|
|
15489
|
+
async getArtifactMeta(token, artifactId, workspaceId) {
|
|
15490
|
+
return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
|
|
15491
|
+
}
|
|
15492
|
+
async downloadArtifact(token, artifactId, workspaceId) {
|
|
15493
|
+
const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
15494
|
+
if (!res.ok) {
|
|
15495
|
+
throw new Error(`artifact download failed: HTTP ${res.status}`);
|
|
15496
|
+
}
|
|
15497
|
+
return res.arrayBuffer();
|
|
15498
|
+
}
|
|
15409
15499
|
reportMessages(token, taskId, messages) {
|
|
15410
15500
|
return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
|
|
15411
15501
|
}
|
|
@@ -16442,6 +16532,19 @@ To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This s
|
|
|
16442
16532
|
`;
|
|
16443
16533
|
}
|
|
16444
16534
|
content += `
|
|
16535
|
+
### Artifacts
|
|
16536
|
+
Upload files for your owner to review in the app.
|
|
16537
|
+
- Your current conversation id is available via env var: $ALOOK_CONVERSATION_ID
|
|
16538
|
+
- Run 'npx @alook/cli sync upload-artifact --agent_id ${task.agentId} --conversation_id $ALOOK_CONVERSATION_ID --file <PATH>'
|
|
16539
|
+
- Use this after generating plans, reports, or any file the owner should review.
|
|
16540
|
+
---
|
|
16541
|
+
|
|
16542
|
+
### Attachments
|
|
16543
|
+
When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
|
|
16544
|
+
Use your Read tool to open them. Images and PDFs are read visually.
|
|
16545
|
+
---
|
|
16546
|
+
`;
|
|
16547
|
+
content += `
|
|
16445
16548
|
### Calendar
|
|
16446
16549
|
You have your own calendar to setup daily routines and reminders.
|
|
16447
16550
|
Schedule future tasks for yourself. At the scheduled time, a new task is dispatched to you with the event as the prompt (task type 'calendar_event').
|
|
@@ -16848,17 +16951,65 @@ function prepare(config2, task) {
|
|
|
16848
16951
|
}
|
|
16849
16952
|
|
|
16850
16953
|
// daemon/prompt.ts
|
|
16851
|
-
function buildPrompt(task) {
|
|
16852
|
-
|
|
16954
|
+
function buildPrompt(task, attachments) {
|
|
16955
|
+
const obj = { type: task.type, instruction: task.prompt };
|
|
16956
|
+
if (attachments && attachments.length > 0) {
|
|
16957
|
+
obj.attachments = attachments.map((a) => ({
|
|
16958
|
+
path: a.path,
|
|
16959
|
+
content_type: a.content_type,
|
|
16960
|
+
filename: a.filename
|
|
16961
|
+
}));
|
|
16962
|
+
}
|
|
16963
|
+
return JSON.stringify(obj);
|
|
16853
16964
|
}
|
|
16854
16965
|
|
|
16855
16966
|
// daemon/session-runner.ts
|
|
16967
|
+
var ATTACHMENTS_BASE = "/tmp/alook-attachments";
|
|
16968
|
+
function sanitizeFilename(name) {
|
|
16969
|
+
return path.basename(name).replace(/[/\\]/g, "_").replace(/\.\./g, "_").slice(0, 255) || "file";
|
|
16970
|
+
}
|
|
16971
|
+
async function cleanupAttachments(taskId) {
|
|
16972
|
+
try {
|
|
16973
|
+
await rm(path.join(ATTACHMENTS_BASE, taskId), { recursive: true, force: true });
|
|
16974
|
+
} catch {}
|
|
16975
|
+
}
|
|
16976
|
+
async function downloadAttachments(client, token, workspaceId, taskId, attachmentIds) {
|
|
16977
|
+
const dir = path.join(ATTACHMENTS_BASE, taskId);
|
|
16978
|
+
await mkdir(dir, { recursive: true });
|
|
16979
|
+
const attachments = [];
|
|
16980
|
+
for (const artId of attachmentIds) {
|
|
16981
|
+
const meta3 = await client.getArtifactMeta(token, artId, workspaceId);
|
|
16982
|
+
const content = await client.downloadArtifact(token, artId, workspaceId);
|
|
16983
|
+
const filename = sanitizeFilename(meta3.filename);
|
|
16984
|
+
const localPath = path.join(dir, `${artId}_${filename}`);
|
|
16985
|
+
await writeFile(localPath, Buffer.from(content));
|
|
16986
|
+
attachments.push({
|
|
16987
|
+
path: localPath,
|
|
16988
|
+
content_type: meta3.content_type,
|
|
16989
|
+
filename: meta3.filename
|
|
16990
|
+
});
|
|
16991
|
+
}
|
|
16992
|
+
return attachments;
|
|
16993
|
+
}
|
|
16856
16994
|
async function runSession(input) {
|
|
16857
16995
|
const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout } = input;
|
|
16858
16996
|
const client = new DaemonClient(serverURL);
|
|
16859
16997
|
const backend = createBackend(provider, cliPath);
|
|
16860
|
-
const prompt = buildPrompt(task);
|
|
16861
16998
|
const { workDir, logFile, timelineDir, env } = prepare({ workspacesRoot }, task);
|
|
16999
|
+
const attachmentIds = task.context?.attachment_ids ?? [];
|
|
17000
|
+
let attachments;
|
|
17001
|
+
if (attachmentIds.length > 0) {
|
|
17002
|
+
try {
|
|
17003
|
+
attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
|
|
17004
|
+
} catch (e) {
|
|
17005
|
+
await cleanupAttachments(task.id);
|
|
17006
|
+
const errMsg = `failed to download attachments: ${e}`;
|
|
17007
|
+
log.error(`Task ${task.id} ${errMsg}`);
|
|
17008
|
+
await client.failTask(token, task.id, errMsg);
|
|
17009
|
+
return;
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
17012
|
+
const prompt = buildPrompt(task, attachments);
|
|
16862
17013
|
const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
|
|
16863
17014
|
if (resumeSessionId) {
|
|
16864
17015
|
log.info(`Task ${task.id} resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
|
|
@@ -16917,6 +17068,7 @@ async function runSession(input) {
|
|
|
16917
17068
|
await flushMessages();
|
|
16918
17069
|
} catch {}
|
|
16919
17070
|
logStream?.end();
|
|
17071
|
+
await cleanupAttachments(task.id);
|
|
16920
17072
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
16921
17073
|
entry.pid = null;
|
|
16922
17074
|
entry.status = "killed";
|
|
@@ -16979,6 +17131,7 @@ async function runSession(input) {
|
|
|
16979
17131
|
process.removeListener("SIGINT", onKill);
|
|
16980
17132
|
if (killed)
|
|
16981
17133
|
return;
|
|
17134
|
+
await cleanupAttachments(task.id);
|
|
16982
17135
|
if (result.status === "completed") {
|
|
16983
17136
|
updateEntry(timelineDir, task.id, (entry) => {
|
|
16984
17137
|
entry.session_id = result.sessionId || null;
|
|
@@ -17024,6 +17177,7 @@ async function main() {
|
|
|
17024
17177
|
await runSession(input);
|
|
17025
17178
|
} catch (e) {
|
|
17026
17179
|
log.error(`session-runner: unhandled error for task ${input.task.id}`, e);
|
|
17180
|
+
await cleanupAttachments(input.task.id);
|
|
17027
17181
|
try {
|
|
17028
17182
|
await client.failTask(input.token, input.task.id, `session-runner crash: ${e}`);
|
|
17029
17183
|
} catch {}
|