@alook/daemon 0.1.8 → 0.1.10

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 CHANGED
@@ -16635,6 +16635,7 @@ var communityBotBinding = sqliteTable("community_bot_binding", {
16635
16635
  userId: text("user_id").primaryKey().references(() => user.id, { onDelete: "cascade" }),
16636
16636
  machineId: text("machine_id").notNull().references(() => communityMachine.id, { onDelete: "restrict" }),
16637
16637
  runtime: text("runtime").notNull(),
16638
+ instruction: text("instruction").notNull().default(""),
16638
16639
  modelName: text("model_name"),
16639
16640
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
16640
16641
  }, (t) => [index("idx_community_bot_binding_machine").on(t.machineId)]);
@@ -16851,12 +16852,19 @@ import * as fs from "fs";
16851
16852
  import * as path from "path";
16852
16853
  // ../shared/src/constants/community.ts
16853
16854
  var MAX_PROFILE_NAME_LENGTH = 100;
16855
+ var MAX_PROFILE_ABOUT_LENGTH = 1000;
16854
16856
  var MAX_MESSAGE_CONTENT_LENGTH = 4000;
16855
16857
  var MAX_EMOJI_BYTES = 32;
16856
16858
  var MAX_ATTACHMENTS_PER_MESSAGE = 10;
16857
16859
  var MAX_ATTACHMENT_SIZE_BYTES = 25 * 1024 * 1024;
16858
16860
  var MAX_ATTACHMENT_THUMBNAIL_SIZE_BYTES = 50 * 1024;
16859
16861
  var MAX_SERVER_ICON_SIZE_BYTES = 5 * 1024 * 1024;
16862
+ var ALLOWED_ICON_MIME_TYPES = [
16863
+ "image/png",
16864
+ "image/jpeg",
16865
+ "image/webp",
16866
+ "image/gif"
16867
+ ];
16860
16868
  var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
16861
16869
  // ../shared/src/utils/slug.ts
16862
16870
  init_nanoid();
@@ -17538,6 +17546,16 @@ var CommunityAgentAttachmentUploadResponseSchema = exports_external.object({
17538
17546
  var CommunityAgentAttachmentDownloadRequestSchema = exports_external.object({
17539
17547
  id: exports_external.string().min(1)
17540
17548
  });
17549
+ var CommunityAgentUpdateProfileRequestSchema = exports_external.object({
17550
+ bio: exports_external.string().max(MAX_PROFILE_ABOUT_LENGTH).optional(),
17551
+ avatar: exports_external.object({
17552
+ filename: exports_external.string().min(1),
17553
+ contentType: exports_external.enum(ALLOWED_ICON_MIME_TYPES),
17554
+ data: exports_external.instanceof(Uint8Array).refine((value) => value.byteLength > 0, "avatar must not be empty").refine((value) => value.byteLength <= MAX_SERVER_ICON_SIZE_BYTES, `avatar must be ≤ ${MAX_SERVER_ICON_SIZE_BYTES} bytes`)
17555
+ }).optional()
17556
+ }).refine((value) => value.bio !== undefined || value.avatar !== undefined, {
17557
+ message: "bio or avatar is required"
17558
+ });
17541
17559
  var CommunityAgentInboxPullRequestSchema = exports_external.object({
17542
17560
  max: exports_external.number().int().min(1).max(200).optional()
17543
17561
  });
@@ -18689,6 +18707,7 @@ function createProxyServerApi(config2) {
18689
18707
  }
18690
18708
  if (!res.ok) {
18691
18709
  const e = new Error(json2?.error ?? `proxy ${method} failed (${res.status})`);
18710
+ e.status = res.status;
18692
18711
  if (json2?.code !== undefined)
18693
18712
  e.code = json2.code;
18694
18713
  if (json2?.hint !== undefined)
@@ -18697,6 +18716,24 @@ function createProxyServerApi(config2) {
18697
18716
  }
18698
18717
  return json2;
18699
18718
  }
18719
+ function isTransientProfileStepError(err) {
18720
+ const message2 = err instanceof Error ? err.message : String(err);
18721
+ const status = typeof err === "object" && err !== null && "status" in err ? err.status : undefined;
18722
+ return typeof status === "number" && status >= 500 && status <= 599 || /upstream returned 5\d\d/.test(message2) || message2.includes("upstream body read failed") || message2.includes("fetch failed") || message2.includes("ECONNRESET") || message2.includes("ETIMEDOUT") || message2.includes("socket hang up") || message2.includes("network");
18723
+ }
18724
+ async function withProfileStepRetry(step) {
18725
+ const maxAttempts = 4;
18726
+ for (let attempt = 0;attempt < maxAttempts; attempt += 1) {
18727
+ try {
18728
+ return await step();
18729
+ } catch (err) {
18730
+ if (!isTransientProfileStepError(err) || attempt === maxAttempts - 1)
18731
+ throw err;
18732
+ await new Promise((resolve) => setTimeout(resolve, Math.min(2000, 150 * 2 ** attempt)));
18733
+ }
18734
+ }
18735
+ throw new Error("profile step retry exhausted");
18736
+ }
18700
18737
  async function callUpload(req) {
18701
18738
  const form = new FormData;
18702
18739
  const blobType = req.file.contentType ?? "application/octet-stream";
@@ -18937,6 +18974,57 @@ function createProxyServerApi(config2) {
18937
18974
  });
18938
18975
  return parseJsonResponse(res, "nap");
18939
18976
  }
18977
+ async function callUpdateProfile(req) {
18978
+ const parsed = CommunityAgentUpdateProfileRequestSchema.safeParse(req);
18979
+ if (!parsed.success) {
18980
+ throw new Error(parsed.error.issues[0]?.message ?? "invalid profile update");
18981
+ }
18982
+ const updated = [];
18983
+ let avatarUrl;
18984
+ if (req.avatar) {
18985
+ const body = await withProfileStepRetry(async () => {
18986
+ const form = new FormData;
18987
+ const blob2 = new Blob([new Uint8Array(req.avatar.data)], { type: req.avatar.contentType });
18988
+ form.append("file", blob2, req.avatar.filename);
18989
+ const res = await fetchImpl(`${base}/api/community/users/me/avatar`, {
18990
+ method: "POST",
18991
+ headers: { authorization: `Bearer ${config2.voucher}` },
18992
+ body: form
18993
+ });
18994
+ return parseJsonResponse(res, "updateProfile avatar");
18995
+ });
18996
+ avatarUrl = body.url;
18997
+ updated.push("avatar");
18998
+ }
18999
+ let bio;
19000
+ if (req.bio !== undefined) {
19001
+ try {
19002
+ const body = await withProfileStepRetry(async () => {
19003
+ const res = await fetchImpl(`${base}/api/community/users/me/profile`, {
19004
+ method: "PATCH",
19005
+ headers: {
19006
+ "content-type": "application/json",
19007
+ authorization: `Bearer ${config2.voucher}`
19008
+ },
19009
+ body: JSON.stringify({ aboutMe: req.bio })
19010
+ });
19011
+ return parseJsonResponse(res, "updateProfile bio");
19012
+ });
19013
+ bio = body.aboutMe;
19014
+ updated.push("bio");
19015
+ } catch (err) {
19016
+ if (avatarUrl !== undefined && err instanceof Error) {
19017
+ err.hint = "avatar was applied; bio was not applied";
19018
+ }
19019
+ throw err;
19020
+ }
19021
+ }
19022
+ return {
19023
+ updated,
19024
+ ...bio !== undefined ? { bio } : {},
19025
+ ...avatarUrl !== undefined ? { avatarUrl } : {}
19026
+ };
19027
+ }
18940
19028
  async function callInboxSnapshot() {
18941
19029
  const res = await fetchImpl(`${base}/api/community/users/me/inbox/snapshot`, {
18942
19030
  method: "GET",
@@ -19014,6 +19102,7 @@ function createProxyServerApi(config2) {
19014
19102
  listMembers: callListMembers,
19015
19103
  attachmentUpload: callUpload,
19016
19104
  attachmentDownload: callDownload,
19105
+ updateProfile: callUpdateProfile,
19017
19106
  reactAdd: callReactAdd,
19018
19107
  markSet: callMarkSet,
19019
19108
  markRemove: callMarkRemove,
@@ -19098,6 +19187,10 @@ function cliCommandsSection() {
19098
19187
  `1. \`${CLI} friend request --username "<name#0042>"\` — ask to friend a user by handle. ` + `Your owner must approve it in DM before it goes through, so expect a \`pending\` ` + `result with a hint (a same-owner sibling bot auto-accepts instead).`,
19099
19188
  `2. \`${CLI} friend list\` — list your friends and pending requests ` + `(\`accepted\`, \`pendingOutgoing\`, \`pendingIncoming\`).`,
19100
19189
  "",
19190
+ "### Settings",
19191
+ "",
19192
+ `1. \`${CLI} setting profile --set-bio <text> --set-avatar <path>\` — update your public bio ` + `and/or avatar. At least one flag is required; pass \`--set-bio ""\` to clear the bio.`,
19193
+ "",
19101
19194
  "### Context Lifecycle",
19102
19195
  "",
19103
19196
  `1. \`${CLI} nap --handoff <file>\` (or \`--text <note>\`) — reset your current session and ` + `start fresh. The required handoff is injected into the new session so your future self can ` + `quickly pick up unfinished work. Never nap on your own; only do it when someone explicitly asks.`,
@@ -22010,6 +22103,8 @@ function parseBearer(authHeader) {
22010
22103
  return m ? m[1].trim() : null;
22011
22104
  }
22012
22105
  var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
22106
+ if ((method === "GET" || method === "PATCH") && pathname === "/api/community/users/me/profile" || method === "POST" && pathname === "/api/community/users/me/avatar")
22107
+ return "profile";
22013
22108
  if (pathname.includes("/attachment"))
22014
22109
  return "attach";
22015
22110
  if (/\/friends(\/|$|\?)/.test(pathname))
@@ -25184,6 +25279,10 @@ function deriveAuditLogSubcommand(pathname, method) {
25184
25279
  return "inboxSnapshot";
25185
25280
  if (/^\/api\/community\/users\/me\/inbox\/ack(\/|$|\?)/.test(canonical))
25186
25281
  return null;
25282
+ if (method === "PATCH" && canonical === "/api/community/users/me/profile")
25283
+ return "profileBioUpdate";
25284
+ if (method === "POST" && canonical === "/api/community/users/me/avatar")
25285
+ return "profileAvatarUpdate";
25187
25286
  if (/^\/api\/community\/bots\/me\/nap(\/|$|\?)/.test(canonical))
25188
25287
  return "nap";
25189
25288
  return null;
@@ -27082,7 +27181,7 @@ async function daemonReplace(opts) {
27082
27181
  }
27083
27182
 
27084
27183
  // src/cli/daemonRunner.ts
27085
- var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge", "attach", "friend"];
27184
+ var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge", "attach", "friend", "profile"];
27086
27185
  var DAEMON_LOG_MAX_BYTES = 8 * 1024 * 1024;
27087
27186
  var DAEMON_ERROR_MESSAGE_MAX_CHARS = 512;
27088
27187
  var DIAGNOSTIC_MAX_LINE_BYTES = 128 * 1024;
@@ -28437,7 +28536,8 @@ function contentTypeFromFilename(filename) {
28437
28536
  }
28438
28537
  function isTransientMutationError(err) {
28439
28538
  const msg = err instanceof Error ? err.message : String(err);
28440
- return /upstream returned 5\d\d/.test(msg) || msg.includes("upstream body read failed") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("socket hang up") || msg.includes("network");
28539
+ const status = typeof err === "object" && err !== null && "status" in err ? err.status : undefined;
28540
+ return typeof status === "number" && status >= 500 && status <= 599 || /upstream returned 5\d\d/.test(msg) || msg.includes("upstream body read failed") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("socket hang up") || msg.includes("network");
28441
28541
  }
28442
28542
  async function withTransientMutationRetry(mutation) {
28443
28543
  const MAX_ATTEMPTS = 4;
@@ -28701,6 +28801,43 @@ async function cmdAttachmentDownload(opts) {
28701
28801
  }
28702
28802
  return result;
28703
28803
  }
28804
+ async function cmdSettingProfile(opts) {
28805
+ const bio = opts.setBio;
28806
+ const avatarPath = opts.setAvatar;
28807
+ if (bio === undefined && avatarPath === undefined) {
28808
+ throw new CliError("setting profile: --set-bio <text> or --set-avatar <path> is required");
28809
+ }
28810
+ if (bio !== undefined && bio.length > MAX_PROFILE_ABOUT_LENGTH) {
28811
+ throw new CliError(`setting profile: bio must be ≤ ${MAX_PROFILE_ABOUT_LENGTH} characters`);
28812
+ }
28813
+ let avatar;
28814
+ if (avatarPath !== undefined) {
28815
+ const fs13 = await import("fs/promises");
28816
+ let bytes;
28817
+ try {
28818
+ bytes = await fs13.readFile(avatarPath);
28819
+ } catch (err) {
28820
+ throw new CliError(`setting profile: cannot read avatar: ${err.message}`);
28821
+ }
28822
+ if (bytes.byteLength === 0)
28823
+ throw new CliError("setting profile: avatar file is empty");
28824
+ if (bytes.byteLength > MAX_SERVER_ICON_SIZE_BYTES) {
28825
+ throw new CliError(`setting profile: avatar too large — ${bytes.byteLength} bytes, max ${MAX_SERVER_ICON_SIZE_BYTES}`);
28826
+ }
28827
+ const pathMod = await import("path");
28828
+ const filename = pathMod.basename(avatarPath);
28829
+ const contentType = contentTypeFromFilename(filename);
28830
+ if (!ALLOWED_ICON_MIME_TYPES.includes(contentType)) {
28831
+ throw new CliError("setting profile: avatar must be png / jpeg / webp / gif");
28832
+ }
28833
+ avatar = { filename, contentType, data: new Uint8Array(bytes) };
28834
+ }
28835
+ const api2 = getApi();
28836
+ return api2.updateProfile({
28837
+ ...bio !== undefined ? { bio } : {},
28838
+ ...avatar ? { avatar } : {}
28839
+ });
28840
+ }
28704
28841
  async function cmdInboxPull(opts) {
28705
28842
  const api2 = getApi();
28706
28843
  const agent2 = agentId(opts);
@@ -28954,6 +29091,12 @@ function buildProgram() {
28954
29091
  const result = await cmdFriendList({ ...globalOpts, ...localOpts });
28955
29092
  printEnvelope({ success: result });
28956
29093
  });
29094
+ const setting = program.command("setting").description("account settings").exitOverride();
29095
+ setting.configureOutput({ writeOut: () => {}, writeErr: () => {} });
29096
+ setting.command("profile").description("update your public bio and/or avatar").option("--set-bio <text>", "set public bio; pass an empty string to clear it").option("--set-avatar <path>", "upload a png, jpeg, webp, or gif avatar").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
29097
+ const result = await cmdSettingProfile({ ...program.opts(), ...this.opts() });
29098
+ printEnvelope({ success: result });
29099
+ });
28957
29100
  program.command("nap").description("end your session and start fresh, carrying a handoff to your reborn self (read the nap rule first)").option("--handoff <file>", "path to your handoff note (your note to your reborn self)").option("--text <note>", "inline handoff note (alternative to --handoff)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
28958
29101
  const localOpts = this.opts();
28959
29102
  const globalOpts = program.opts();
package/dist/index.js CHANGED
@@ -82,6 +82,10 @@ function cliCommandsSection() {
82
82
  `1. \`${CLI} friend request --username "<name#0042>"\` — ask to friend a user by handle. ` + `Your owner must approve it in DM before it goes through, so expect a \`pending\` ` + `result with a hint (a same-owner sibling bot auto-accepts instead).`,
83
83
  `2. \`${CLI} friend list\` — list your friends and pending requests ` + `(\`accepted\`, \`pendingOutgoing\`, \`pendingIncoming\`).`,
84
84
  "",
85
+ "### Settings",
86
+ "",
87
+ `1. \`${CLI} setting profile --set-bio <text> --set-avatar <path>\` — update your public bio ` + `and/or avatar. At least one flag is required; pass \`--set-bio ""\` to clear the bio.`,
88
+ "",
85
89
  "### Context Lifecycle",
86
90
  "",
87
91
  `1. \`${CLI} nap --handoff <file>\` (or \`--text <note>\`) — reset your current session and ` + `start fresh. The required handoff is injected into the new session so your future self can ` + `quickly pick up unfinished work. Never nap on your own; only do it when someone explicitly asks.`,
@@ -21638,6 +21642,7 @@ var communityBotBinding = sqliteTable("community_bot_binding", {
21638
21642
  userId: text("user_id").primaryKey().references(() => user.id, { onDelete: "cascade" }),
21639
21643
  machineId: text("machine_id").notNull().references(() => communityMachine.id, { onDelete: "restrict" }),
21640
21644
  runtime: text("runtime").notNull(),
21645
+ instruction: text("instruction").notNull().default(""),
21641
21646
  modelName: text("model_name"),
21642
21647
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
21643
21648
  }, (t) => [index("idx_community_bot_binding_machine").on(t.machineId)]);
@@ -21947,6 +21952,8 @@ function parseBearer(authHeader) {
21947
21952
  return m ? m[1].trim() : null;
21948
21953
  }
21949
21954
  var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
21955
+ if ((method === "GET" || method === "PATCH") && pathname === "/api/community/users/me/profile" || method === "POST" && pathname === "/api/community/users/me/avatar")
21956
+ return "profile";
21950
21957
  if (pathname.includes("/attachment"))
21951
21958
  return "attach";
21952
21959
  if (/\/friends(\/|$|\?)/.test(pathname))
@@ -23689,6 +23696,10 @@ function deriveAuditLogSubcommand(pathname, method) {
23689
23696
  return "inboxSnapshot";
23690
23697
  if (/^\/api\/community\/users\/me\/inbox\/ack(\/|$|\?)/.test(canonical))
23691
23698
  return null;
23699
+ if (method === "PATCH" && canonical === "/api/community/users/me/profile")
23700
+ return "profileBioUpdate";
23701
+ if (method === "POST" && canonical === "/api/community/users/me/avatar")
23702
+ return "profileAvatarUpdate";
23692
23703
  if (/^\/api\/community\/bots\/me\/nap(\/|$|\?)/.test(canonical))
23693
23704
  return "nap";
23694
23705
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/daemon",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",
@@ -48,16 +48,16 @@
48
48
  "dependencies": {
49
49
  "commander": "^15.0.0",
50
50
  "sharp": "^0.35.0",
51
- "ws": "^8.21.2"
51
+ "ws": "^8.21.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@alook/shared": "workspace:*",
55
55
  "@types/node": "^20.0.0",
56
56
  "@types/ws": "^8.18.1",
57
57
  "eslint": "^9.39.5",
58
- "tsx": "^4.23.10",
58
+ "tsx": "^4.23.12",
59
59
  "typescript": "^6.0.3",
60
- "typescript-eslint": "^8.66.0",
60
+ "typescript-eslint": "^8.67.0",
61
61
  "vitest": "^4.1.10"
62
62
  }
63
63
  }