@slock-ai/daemon 0.50.0 → 0.51.0

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
@@ -56,19 +56,78 @@ function loadAgentContext(env = process.env) {
56
56
  const agentId = env.SLOCK_AGENT_ID;
57
57
  const serverUrl = env.SLOCK_SERVER_URL;
58
58
  const serverId = env.SLOCK_SERVER_ID ?? null;
59
+ const activeCapabilities = env.SLOCK_AGENT_ACTIVE_CAPABILITIES ? env.SLOCK_AGENT_ACTIVE_CAPABILITIES.split(",").map((cap) => cap.trim()).filter(Boolean) : null;
59
60
  if (!agentId) throw new AgentBootstrapError("MISSING_AGENT_ID", "SLOCK_AGENT_ID is required");
60
61
  if (!serverUrl) throw new AgentBootstrapError("MISSING_SERVER_URL", "SLOCK_SERVER_URL is required");
62
+ const agentProxyUrl = env.SLOCK_AGENT_PROXY_URL;
63
+ const agentProxyToken = env.SLOCK_AGENT_PROXY_TOKEN;
64
+ const agentProxyTokenFile = env.SLOCK_AGENT_PROXY_TOKEN_FILE;
65
+ if (agentProxyUrl || agentProxyToken || agentProxyTokenFile) {
66
+ if (!agentProxyUrl) {
67
+ throw new AgentBootstrapError("MISSING_AGENT_PROXY_URL", "SLOCK_AGENT_PROXY_URL is required when agent proxy auth is set");
68
+ }
69
+ if (agentProxyToken && agentProxyTokenFile) {
70
+ throw new AgentBootstrapError(
71
+ "MULTIPLE_AGENT_PROXY_TOKENS",
72
+ "Set only one of SLOCK_AGENT_PROXY_TOKEN or SLOCK_AGENT_PROXY_TOKEN_FILE"
73
+ );
74
+ }
75
+ const token = agentProxyToken ?? (agentProxyTokenFile ? readTokenFromFile(agentProxyTokenFile) : null);
76
+ if (!token) {
77
+ throw new AgentBootstrapError(
78
+ "MISSING_AGENT_PROXY_TOKEN",
79
+ "SLOCK_AGENT_PROXY_TOKEN_FILE or SLOCK_AGENT_PROXY_TOKEN is required when SLOCK_AGENT_PROXY_URL is set"
80
+ );
81
+ }
82
+ return {
83
+ agentId,
84
+ serverUrl: agentProxyUrl,
85
+ serverId,
86
+ token,
87
+ clientMode: "managed-runner",
88
+ secretSource: agentProxyTokenFile ? "agent-proxy-token-file" : "agent-proxy-token-env",
89
+ activeCapabilities
90
+ };
91
+ }
92
+ const agentCredentialFile = env.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
93
+ if (agentCredentialFile) {
94
+ return {
95
+ agentId,
96
+ serverUrl,
97
+ serverId,
98
+ token: readTokenFromFile(agentCredentialFile),
99
+ clientMode: "self-hosted-runner",
100
+ secretSource: "agent-credential-file",
101
+ activeCapabilities
102
+ };
103
+ }
61
104
  const tokenFile = env.SLOCK_AGENT_TOKEN_FILE;
62
105
  if (tokenFile) {
63
- return { agentId, serverUrl, serverId, token: readTokenFromFile(tokenFile), tokenSource: "token-file" };
106
+ return {
107
+ agentId,
108
+ serverUrl,
109
+ serverId,
110
+ token: readTokenFromFile(tokenFile),
111
+ clientMode: "legacy-machine",
112
+ secretSource: "legacy-token-file",
113
+ activeCapabilities
114
+ };
64
115
  }
65
116
  const tokenLiteral = env.SLOCK_AGENT_TOKEN;
66
117
  if (tokenLiteral) {
67
- return { agentId, serverUrl, serverId, token: tokenLiteral, tokenSource: "env" };
118
+ return {
119
+ agentId,
120
+ serverUrl,
121
+ serverId,
122
+ token: tokenLiteral,
123
+ clientMode: "legacy-machine",
124
+ secretSource: "legacy-token-env",
125
+ activeCapabilities
126
+ };
68
127
  }
69
128
  throw new AgentBootstrapError(
70
129
  "MISSING_TOKEN",
71
- "Neither SLOCK_AGENT_TOKEN_FILE nor SLOCK_AGENT_TOKEN is set. The daemon should inject one of these when spawning the agent process."
130
+ "Neither SLOCK_AGENT_PROXY_TOKEN_FILE, SLOCK_AGENT_PROXY_TOKEN, SLOCK_AGENT_CREDENTIAL_KEY_FILE, SLOCK_AGENT_TOKEN_FILE nor SLOCK_AGENT_TOKEN is set. The daemon should inject one of these when spawning the agent process."
72
131
  );
73
132
  }
74
133
 
@@ -88,7 +147,8 @@ function registerWhoamiCommand(parent) {
88
147
  agentId: ctx.agentId,
89
148
  serverUrl: ctx.serverUrl,
90
149
  serverId: ctx.serverId,
91
- tokenSource: ctx.tokenSource
150
+ clientMode: ctx.clientMode,
151
+ secretSource: ctx.secretSource
92
152
  }
93
153
  });
94
154
  });
@@ -14203,6 +14263,54 @@ var ApiClient = class {
14203
14263
  constructor(ctx) {
14204
14264
  this.ctx = ctx;
14205
14265
  }
14266
+ usesAgentApiSurface() {
14267
+ return this.ctx.clientMode === "managed-runner" || this.ctx.clientMode === "self-hosted-runner";
14268
+ }
14269
+ rewriteAgentCredentialPath(pathname) {
14270
+ if (!this.usesAgentApiSurface()) return pathname;
14271
+ const attachmentDownload = /^\/api\/attachments\/([^/?]+)(.*)$/.exec(pathname);
14272
+ if (attachmentDownload) {
14273
+ return `/internal/agent-api/attachments/${attachmentDownload[1]}${attachmentDownload[2] ?? ""}`;
14274
+ }
14275
+ const agentPrefix = `/internal/agent/${encodeURIComponent(this.ctx.agentId)}`;
14276
+ if (!pathname.startsWith(agentPrefix)) return pathname;
14277
+ const suffix = pathname.slice(agentPrefix.length);
14278
+ if (suffix === "/server") return "/internal/agent-api/server";
14279
+ if (suffix === "/send") return "/internal/agent-api/send";
14280
+ if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
14281
+ if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
14282
+ if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
14283
+ if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
14284
+ if (suffix === "/upload") return "/internal/agent-api/upload";
14285
+ if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
14286
+ if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
14287
+ if (suffix === "/prepare-action") return "/internal/agent-api/prepare-action";
14288
+ if (suffix === "/tasks" || suffix.startsWith("/tasks?") || suffix.startsWith("/tasks/")) {
14289
+ return `/internal/agent-api${suffix}`;
14290
+ }
14291
+ if (suffix === "/reminders" || suffix.startsWith("/reminders?") || suffix.startsWith("/reminders/")) {
14292
+ return `/internal/agent-api${suffix}`;
14293
+ }
14294
+ if (suffix === "/receive" || suffix.startsWith("/receive?")) {
14295
+ return "/internal/agent-api/events?since=latest";
14296
+ }
14297
+ const reaction = /^\/messages\/([^/]+)\/reactions$/.exec(suffix);
14298
+ if (reaction) {
14299
+ return `/internal/agent-api/messages/${reaction[1]}/reactions`;
14300
+ }
14301
+ const channelMembership = /^\/channels\/([^/]+)\/(join|leave)$/.exec(suffix);
14302
+ if (channelMembership) {
14303
+ return `/internal/agent-api/channels/${channelMembership[1]}/${channelMembership[2]}`;
14304
+ }
14305
+ return pathname;
14306
+ }
14307
+ normalizeAgentCredentialResponse(pathname, data) {
14308
+ if (!this.usesAgentApiSurface()) return data;
14309
+ if (!pathname.includes("/internal/agent-api/events")) return data;
14310
+ const value = data;
14311
+ if (!Array.isArray(value.events)) return data;
14312
+ return { ...data, messages: value.events };
14313
+ }
14206
14314
  buildAuthHeaders() {
14207
14315
  const headers = {
14208
14316
  "Authorization": `Bearer ${this.ctx.token}`,
@@ -14237,9 +14345,13 @@ var ApiClient = class {
14237
14345
  return { ok: res.ok, status: res.status, data, error: error48, errorCode };
14238
14346
  }
14239
14347
  async request(method, pathname, body) {
14348
+ pathname = this.rewriteAgentCredentialPath(pathname);
14240
14349
  const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14241
14350
  const headers = this.buildAuthHeaders();
14242
14351
  headers["Content-Type"] = "application/json";
14352
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14353
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14354
+ }
14243
14355
  const dispatcher = buildFetchDispatcher(url2);
14244
14356
  const init = {
14245
14357
  method,
@@ -14248,17 +14360,26 @@ var ApiClient = class {
14248
14360
  };
14249
14361
  if (dispatcher) init.dispatcher = dispatcher;
14250
14362
  const res = await fetch(url2, init);
14251
- return this.parseJsonResponse(res);
14363
+ const parsed = await this.parseJsonResponse(res);
14364
+ if (parsed.ok && parsed.data !== null) {
14365
+ parsed.data = this.normalizeAgentCredentialResponse(pathname, parsed.data);
14366
+ }
14367
+ return parsed;
14252
14368
  }
14253
14369
  // Multipart upload. Caller builds the FormData (file part + any text fields).
14254
14370
  // Content-Type intentionally omitted so fetch sets the correct multipart
14255
14371
  // boundary itself.
14256
14372
  async requestMultipart(method, pathname, form) {
14373
+ pathname = this.rewriteAgentCredentialPath(pathname);
14257
14374
  const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14258
14375
  const dispatcher = buildFetchDispatcher(url2);
14376
+ const headers = this.buildAuthHeaders();
14377
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14378
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14379
+ }
14259
14380
  const init = {
14260
14381
  method,
14261
- headers: this.buildAuthHeaders(),
14382
+ headers,
14262
14383
  body: form
14263
14384
  };
14264
14385
  if (dispatcher) init.dispatcher = dispatcher;
@@ -14269,11 +14390,16 @@ var ApiClient = class {
14269
14390
  // For non-JSON downloads (binary attachments). Caller is responsible for
14270
14391
  // consuming the body. On non-2xx, attempts to surface a JSON error.
14271
14392
  async requestRaw(method, pathname) {
14393
+ pathname = this.rewriteAgentCredentialPath(pathname);
14272
14394
  const url2 = new URL(pathname, this.ctx.serverUrl).toString();
14273
14395
  const dispatcher = buildFetchDispatcher(url2);
14396
+ const headers = this.buildAuthHeaders();
14397
+ if (this.ctx.activeCapabilities && this.ctx.activeCapabilities.length > 0) {
14398
+ headers["X-Slock-Agent-Active-Capabilities"] = this.ctx.activeCapabilities.join(",");
14399
+ }
14274
14400
  const init = {
14275
14401
  method,
14276
- headers: this.buildAuthHeaders(),
14402
+ headers,
14277
14403
  redirect: "follow"
14278
14404
  };
14279
14405
  if (dispatcher) init.dispatcher = dispatcher;
@@ -14802,12 +14928,15 @@ function formatMessages(messages) {
14802
14928
  function formatHistoryMessageLine(m) {
14803
14929
  const senderName = m.senderName ?? m.sender_name ?? "unknown";
14804
14930
  const senderDescription = m.senderDescription ?? m.sender_description ?? null;
14931
+ const messageId = m.id ?? m.message_id ?? "-";
14932
+ const createdAt = m.createdAt ?? m.timestamp ?? null;
14933
+ const senderType = m.senderType ?? m.sender_type ?? null;
14805
14934
  const headerParts = [
14806
14935
  `seq=${m.seq}`,
14807
- `msg=${m.id || "-"}`,
14808
- `time=${m.createdAt ? toLocalTime(m.createdAt) : "-"}`
14936
+ `msg=${messageId}`,
14937
+ `time=${createdAt ? toLocalTime(createdAt) : "-"}`
14809
14938
  ];
14810
- if (m.senderType) headerParts.push(`type=${m.senderType}`);
14939
+ if (senderType) headerParts.push(`type=${senderType}`);
14811
14940
  if (m.threadId) headerParts.push(`threadId=${m.threadId}`);
14812
14941
  if ((m.replyCount ?? 0) > 0) headerParts.push(`replyCount=${m.replyCount}`);
14813
14942
  const attachSuffix = formatAttachmentSuffix(m.attachments);
@@ -14981,6 +15110,30 @@ function rejectSendDraftStdin(content, target) {
14981
15110
  ].join("\n")
14982
15111
  );
14983
15112
  }
15113
+ function formatHeldSendOutput(target, data) {
15114
+ const newMessageCount = data.newMessageCount ?? 0;
15115
+ const shownMessageCount = data.shownMessageCount ?? data.heldMessages?.length ?? 0;
15116
+ const omittedMessageCount = data.omittedMessageCount ?? Math.max(0, newMessageCount - shownMessageCount);
15117
+ const heldHistory = data.heldMessages && data.heldMessages.length > 0 ? `
15118
+
15119
+ ${formatHistory(target, { messages: data.heldMessages })}` : "";
15120
+ const mentionNote = (data.mentionAnnotation?.formalMentionCount ?? 0) > 0 ? `
15121
+
15122
+ Note: ${data.mentionAnnotation.formalMentionCount} of these messages formally @mention you.` : "";
15123
+ const omittedNote = omittedMessageCount > 0 ? ` ${omittedMessageCount} additional newer message${omittedMessageCount === 1 ? "" : "s"} omitted from this notice; use slock message read explicitly if you need more context.` : "";
15124
+ return `Freshness hold: showing latest ${shownMessageCount} of ${newMessageCount} newer message${newMessageCount === 1 ? "" : "s"}.${omittedNote}
15125
+ Your message has been saved as a draft. Review the bounded context shown here, then choose one path.${mentionNote}${heldHistory}
15126
+
15127
+ To update the draft, send revised content normally:
15128
+ slock message send --target "${target}" <<'EOF'
15129
+ revised message
15130
+ EOF
15131
+ To send the current draft unchanged:
15132
+ slock message send --send-draft --target "${target}"
15133
+ ` + (data.continueAnywaySuggested ? `If repeated updates keep blocking the same draft and this is still the right reply, you may use:
15134
+ slock message send --send-draft --anyway --target "${target}"
15135
+ ` : "");
15136
+ }
14984
15137
  function registerSendCommand(parent) {
14985
15138
  parent.command("send").description("Send a message to a channel, DM, or thread").argument("[content...]", "Unsupported positional message content. Pipe content to stdin instead.").requiredOption("--target <target>", "Target: '#channel', 'dm:@peer', '#channel:threadId', 'dm:@peer:threadId'").option("--send-draft", "Send the current saved draft after reviewing newer messages").option("--anyway", "Escape hatch: send a saved draft even if freshness re-check is still stale").option("--content <content>", "Unsupported. Pipe message content to stdin instead.").option(
14986
15139
  "--attachment-id <id>",
@@ -15086,26 +15239,7 @@ function registerSendCommand(parent) {
15086
15239
  reholdCount: previousDraftReholdCount + 1,
15087
15240
  seenUpToSeq: data.seenUpToSeq
15088
15241
  });
15089
- const heldHistory = data.heldMessages && data.heldMessages.length > 0 ? `
15090
-
15091
- ${formatHistory(opts.target, { messages: data.heldMessages })}` : "";
15092
- const mentionNote = (data.mentionAnnotation?.formalMentionCount ?? 0) > 0 ? `
15093
-
15094
- Note: ${data.mentionAnnotation.formalMentionCount} of these messages formally @mention you.` : "";
15095
- process.stdout.write(
15096
- `Not sent yet \u2014 ${data.newMessageCount ?? 0} newer message(s) arrived.
15097
- Your message has been saved as a draft. Read the new messages below, then choose one path.${mentionNote}${heldHistory}
15098
-
15099
- To update the draft, send revised content normally:
15100
- slock message send --target "${opts.target}" <<'EOF'
15101
- revised message
15102
- EOF
15103
- To send the current draft unchanged:
15104
- slock message send --send-draft --target "${opts.target}"
15105
- ` + (data.continueAnywaySuggested ? `If repeated updates keep blocking the same draft and this is still the right reply, you may use:
15106
- slock message send --send-draft --anyway --target "${opts.target}"
15107
- ` : "")
15108
- );
15242
+ process.stdout.write(formatHeldSendOutput(opts.target, data));
15109
15243
  return;
15110
15244
  }
15111
15245
  clearSavedDraft(ctx.agentId, opts.target);
@@ -15138,6 +15272,9 @@ async function drainInbox(ctx, opts) {
15138
15272
  fail(code, res.error ?? `HTTP ${res.status}`);
15139
15273
  }
15140
15274
  const messages = res.data?.messages ?? [];
15275
+ if (ctx.clientMode === "managed-runner" || ctx.clientMode === "self-hosted-runner") {
15276
+ return { messages };
15277
+ }
15141
15278
  const seqs = messages.map((m) => m.seq).filter((s) => Number.isInteger(s) && s > 0);
15142
15279
  if (seqs.length === 0) return { messages };
15143
15280
  const ack = await client.request("POST", `${agentPath}/receive-ack`, { seqs });
@@ -15880,8 +16017,18 @@ function readAvatarFile(avatarFile) {
15880
16017
  }
15881
16018
  return { filename, buffer, mimeType };
15882
16019
  }
16020
+ function normalizeAvatarUrl(avatarUrl) {
16021
+ const trimmed = avatarUrl.trim();
16022
+ if (trimmed.length === 0) {
16023
+ fail("INVALID_ARG", "--avatar-url must not be empty");
16024
+ }
16025
+ if (!trimmed.startsWith("pixel:")) {
16026
+ fail("INVALID_ARG", "--avatar-url currently supports only pixel avatar URLs; use --avatar-file for image uploads");
16027
+ }
16028
+ return trimmed;
16029
+ }
15883
16030
  function registerProfileUpdateCommand(parent) {
15884
- parent.command("update").description("Update your own profile").option("--avatar-file <path>", "Path to a local image file to use as your avatar").option("--display-name <name>", "Set your display name (non-empty)").option("--description <text>", "Set your profile description (non-empty)").option("--json", "Emit machine-readable JSON").action(async (opts) => {
16031
+ parent.command("update").description("Update your own profile").option("--avatar-file <path>", "Path to a local image file to use as your avatar").option("--avatar-url <value>", "Set a pixel avatar URL such as pixel:random:<seed>").option("--display-name <name>", "Set your display name (non-empty)").option("--description <text>", "Set your profile description (non-empty)").option("--json", "Emit machine-readable JSON").action(async (opts) => {
15885
16032
  let ctx;
15886
16033
  try {
15887
16034
  ctx = loadAgentContext();
@@ -15890,10 +16037,18 @@ function registerProfileUpdateCommand(parent) {
15890
16037
  throw err;
15891
16038
  }
15892
16039
  const hasAvatar = opts.avatarFile !== void 0;
16040
+ const hasAvatarUrl = opts.avatarUrl !== void 0;
15893
16041
  const hasDisplayName = opts.displayName !== void 0;
15894
16042
  const hasDescription = opts.description !== void 0;
15895
- if (!hasAvatar && !hasDisplayName && !hasDescription) {
15896
- fail("INVALID_ARG", "Provide at least one of --avatar-file, --display-name, or --description");
16043
+ if (!hasAvatar && !hasAvatarUrl && !hasDisplayName && !hasDescription) {
16044
+ fail("INVALID_ARG", "Provide at least one of --avatar-file, --avatar-url, --display-name, or --description");
16045
+ }
16046
+ if (hasAvatar && hasAvatarUrl) {
16047
+ fail("INVALID_ARG", "Use either --avatar-file or --avatar-url, not both");
16048
+ }
16049
+ let normalizedAvatarUrl;
16050
+ if (hasAvatarUrl) {
16051
+ normalizedAvatarUrl = normalizeAvatarUrl(opts.avatarUrl);
15897
16052
  }
15898
16053
  let trimmedDisplayName;
15899
16054
  if (hasDisplayName) {
@@ -15915,8 +16070,11 @@ function registerProfileUpdateCommand(parent) {
15915
16070
  }
15916
16071
  const client = new ApiClient(ctx);
15917
16072
  let latestProfile = null;
15918
- if (hasDisplayName || hasDescription) {
16073
+ if (hasAvatarUrl || hasDisplayName || hasDescription) {
15919
16074
  const body = {};
16075
+ if (hasAvatarUrl) {
16076
+ body.avatarUrl = normalizedAvatarUrl;
16077
+ }
15920
16078
  if (hasDisplayName) {
15921
16079
  body.displayName = trimmedDisplayName;
15922
16080
  }
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-EXJF5JKE.js";
12
+ } from "./chunk-NAMH7BB6.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-KNMCE6WB.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-EXJF5JKE.js";
6
+ } from "./chunk-NAMH7BB6.js";
7
7
  import "./chunk-KNMCE6WB.js";
8
8
 
9
9
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"