@slock-ai/daemon 0.44.2 → 0.46.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.
@@ -110,9 +110,109 @@ function buildFetchDispatcher(targetUrl, env) {
110
110
  return dispatcher;
111
111
  }
112
112
 
113
+ // src/chatBridgeRequest.ts
114
+ var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
115
+ process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
116
+ 10
117
+ ) || 6e4;
118
+ var ChatBridgeToolTimeoutError = class extends Error {
119
+ toolName;
120
+ target;
121
+ timeoutMs;
122
+ durationMs;
123
+ constructor(toolName, target, timeoutMs, durationMs) {
124
+ super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
125
+ this.name = "ChatBridgeToolTimeoutError";
126
+ this.toolName = toolName;
127
+ this.target = target;
128
+ this.timeoutMs = timeoutMs;
129
+ this.durationMs = durationMs;
130
+ }
131
+ };
132
+ function describeError(err) {
133
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
134
+ return String(err);
135
+ }
136
+ async function executeJsonRequest(url, init, {
137
+ toolName,
138
+ target = null,
139
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
140
+ fetchImpl,
141
+ now = () => Date.now(),
142
+ warn = (message) => logger.warn(message)
143
+ }) {
144
+ const startedAt = now();
145
+ const timeoutController = new AbortController();
146
+ const signals = [timeoutController.signal];
147
+ if (init.signal) signals.push(init.signal);
148
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
149
+ const timeout = setTimeout(() => {
150
+ timeoutController.abort();
151
+ }, timeoutMs);
152
+ timeout.unref?.();
153
+ try {
154
+ const response = await fetchImpl(url, { ...init, signal });
155
+ const data = await response.json();
156
+ return { response, data, durationMs: now() - startedAt };
157
+ } catch (err) {
158
+ const durationMs = now() - startedAt;
159
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
160
+ warn(
161
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
162
+ );
163
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
164
+ }
165
+ warn(
166
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
167
+ );
168
+ throw err;
169
+ } finally {
170
+ clearTimeout(timeout);
171
+ }
172
+ }
173
+ async function executeResponseRequest(url, init, {
174
+ toolName,
175
+ target = null,
176
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
177
+ fetchImpl,
178
+ now = () => Date.now(),
179
+ warn = (message) => logger.warn(message)
180
+ }) {
181
+ const startedAt = now();
182
+ const timeoutController = new AbortController();
183
+ const signals = [timeoutController.signal];
184
+ if (init.signal) signals.push(init.signal);
185
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
186
+ const timeout = setTimeout(() => {
187
+ timeoutController.abort();
188
+ }, timeoutMs);
189
+ timeout.unref?.();
190
+ try {
191
+ const response = await fetchImpl(url, { ...init, signal });
192
+ return { response, durationMs: now() - startedAt };
193
+ } catch (err) {
194
+ const durationMs = now() - startedAt;
195
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
196
+ warn(
197
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
198
+ );
199
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
200
+ }
201
+ warn(
202
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
203
+ );
204
+ throw err;
205
+ } finally {
206
+ clearTimeout(timeout);
207
+ }
208
+ }
209
+
113
210
  export {
114
211
  subscribeDaemonLogs,
115
212
  logger,
116
213
  buildWebSocketOptions,
117
- buildFetchDispatcher
214
+ buildFetchDispatcher,
215
+ DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
216
+ executeJsonRequest,
217
+ executeResponseRequest
118
218
  };
package/dist/cli/index.js CHANGED
@@ -258,10 +258,11 @@ function formatServerInfo(data) {
258
258
  const humans = data.humans ?? [];
259
259
  text += formatRuntimeContext(data.runtimeContext);
260
260
  text += "### Channels\n";
261
- text += 'Visible public channels may appear even when `joined=false`. Use `slock message read --channel "#name"` to inspect them. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a channel you have joined, use `slock channel leave --target "#name"`. To stop following a thread, use `slock thread unfollow --target "#name:shortid"`.\n';
261
+ text += 'Visible public channels may appear even when `joined=false`. Private channels are shown only when you are a member; do not disclose private-channel names, membership, or content outside that channel. Use `slock message read --channel "#name"` to inspect visible channels. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a channel you have joined, use `slock channel leave --target "#name"`. To stop following a thread, use `slock thread unfollow --target "#name:shortid"`.\n';
262
262
  if (channels.length > 0) {
263
263
  for (const t of channels) {
264
- const status = t.joined ? "joined" : "not joined";
264
+ const visibility = t.type === "private" ? "private" : "public";
265
+ const status = `${visibility}, ${t.joined ? "joined" : "not joined"}`;
265
266
  text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
266
267
  ` : ` - #${t.name} [${status}]
267
268
  `;
@@ -314,10 +315,12 @@ function formatChannelMembers(data) {
314
315
  text += " (none)\n";
315
316
  }
316
317
  text += "\n### Humans\n";
318
+ text += "Role labels show server-level owner/admin authority; no role label means ordinary member.\n";
317
319
  if (humans.length > 0) {
318
320
  for (const u of humans) {
319
- text += u.description ? ` - @${u.name} \u2014 ${u.description}
320
- ` : ` - @${u.name}
321
+ const role = u.role && u.role !== "member" ? ` (${u.role})` : "";
322
+ text += u.description ? ` - @${u.name}${role} \u2014 ${u.description}
323
+ ` : ` - @${u.name}${role}
321
324
  `;
322
325
  }
323
326
  } else {
@@ -815,8 +818,23 @@ function registerReadCommand(parent) {
815
818
  }
816
819
 
817
820
  // src/commands/message/search.ts
821
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
822
+ function normalizeMemberHandleRef(raw) {
823
+ const trimmed = raw.trim();
824
+ if (!trimmed) {
825
+ fail("INVALID_ARG", "--sender must not be empty");
826
+ }
827
+ if (UUID_RE2.test(trimmed)) {
828
+ fail("INVALID_ARG", "--sender expects a member handle like @alice, not a UUID");
829
+ }
830
+ const handle = trimmed.replace(/^@/, "").trim();
831
+ if (!handle) {
832
+ fail("INVALID_ARG", "--sender handle must not be empty");
833
+ }
834
+ return handle;
835
+ }
818
836
  function registerSearchCommand(parent) {
819
- parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <id>", "Restrict to messages by this sender id").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
837
+ parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <handle>", "Restrict to messages by sender handle, e.g. @alice").option("--sort <mode>", "Sort results by relevance or recent (default: relevance)").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
820
838
  let ctx;
821
839
  try {
822
840
  ctx = loadAgentContext();
@@ -832,10 +850,14 @@ function registerSearchCommand(parent) {
832
850
  }
833
851
  limit = n;
834
852
  }
853
+ if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
854
+ fail("INVALID_ARG", `--sort must be "relevance" or "recent"; got ${opts.sort}`);
855
+ }
835
856
  const params = new URLSearchParams();
836
857
  params.set("q", opts.query);
837
858
  if (opts.channel) params.set("channel", opts.channel);
838
- if (opts.sender) params.set("senderId", opts.sender);
859
+ if (opts.sender) params.set("sender", normalizeMemberHandleRef(opts.sender));
860
+ if (opts.sort) params.set("sort", opts.sort);
839
861
  if (opts.before) params.set("before", opts.before);
840
862
  if (opts.after) params.set("after", opts.after);
841
863
  if (limit !== void 0) params.set("limit", String(limit));
@@ -845,7 +867,7 @@ function registerSearchCommand(parent) {
845
867
  `/internal/agent/${encodeURIComponent(ctx.agentId)}/search?${params.toString()}`
846
868
  );
847
869
  if (!res.ok) {
848
- const code = res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
870
+ const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED");
849
871
  fail(code, res.error ?? `HTTP ${res.status}`);
850
872
  }
851
873
  process.stdout.write(formatSearchResults(opts.query, res.data) + "\n");
@@ -855,7 +877,8 @@ function registerSearchCommand(parent) {
855
877
  // src/commands/attachment/upload.ts
856
878
  import { existsSync, statSync, readFileSync } from "fs";
857
879
  import { basename } from "path";
858
- var MAX_BYTES = 10 * 1024 * 1024;
880
+ var MAX_ATTACHMENT_UPLOAD_BYTES = 50 * 1024 * 1024;
881
+ var MAX_ATTACHMENT_UPLOAD_LABEL = "50MB";
859
882
  var FILENAME_MIME_MAP = {
860
883
  ".jpg": "image/jpeg",
861
884
  ".jpeg": "image/jpeg",
@@ -912,8 +935,33 @@ function inferUploadMimeType(filename, buffer, explicitMimeType) {
912
935
  const explicit = normalizeExplicitMimeType(explicitMimeType);
913
936
  return explicit || inferMimeTypeFromBuffer(buffer) || inferMimeTypeFromFilename(filename) || "application/octet-stream";
914
937
  }
938
+ function validateUploadFileSize(size) {
939
+ if (size > MAX_ATTACHMENT_UPLOAD_BYTES) {
940
+ throw new AttachmentUploadArgError(
941
+ "INVALID_ARG",
942
+ `--path is ${formatBytes(size)}; max upload size is ${MAX_ATTACHMENT_UPLOAD_LABEL}`
943
+ );
944
+ }
945
+ if (size === 0) {
946
+ throw new AttachmentUploadArgError(
947
+ "INVALID_ARG",
948
+ "--path is empty; refusing to upload a 0-byte attachment"
949
+ );
950
+ }
951
+ }
952
+ function formatBytes(bytes) {
953
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0B";
954
+ const units = ["B", "KB", "MB", "GB"];
955
+ let value = bytes;
956
+ let unitIndex = 0;
957
+ while (value >= 1024 && unitIndex < units.length - 1) {
958
+ value /= 1024;
959
+ unitIndex += 1;
960
+ }
961
+ return `${unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
962
+ }
915
963
  function registerAttachmentUploadCommand(parent) {
916
- parent.command("upload").description("Upload a local file as an attachment (max 10MB)").requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
964
+ parent.command("upload").description(`Upload a local file as an attachment (max ${MAX_ATTACHMENT_UPLOAD_LABEL})`).requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
917
965
  "--channel <target>",
918
966
  "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
919
967
  ).option("--mime-type <type>", "Explicit MIME type override, e.g. image/png").action(async (opts) => {
@@ -931,11 +979,11 @@ function registerAttachmentUploadCommand(parent) {
931
979
  if (!stat.isFile()) {
932
980
  fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
933
981
  }
934
- if (stat.size > MAX_BYTES) {
935
- fail(
936
- "INVALID_ARG",
937
- `--path is ${stat.size} bytes; max upload size is ${MAX_BYTES} bytes (10MB)`
938
- );
982
+ try {
983
+ validateUploadFileSize(stat.size);
984
+ } catch (err) {
985
+ if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
986
+ throw err;
939
987
  }
940
988
  if (!opts.channel) {
941
989
  fail(
@@ -1325,6 +1373,9 @@ function randomHex(length) {
1325
1373
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1326
1374
  }
1327
1375
 
1376
+ // ../shared/src/attachmentPreview.ts
1377
+ var CSV_PREVIEW_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
1378
+
1328
1379
  // ../shared/src/testing/failpoints.ts
1329
1380
  var NoopFailpointRegistry = class {
1330
1381
  get enabled() {
package/dist/core.js CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-NM2MFLQ7.js";
12
+ } from "./chunk-Q4XUZB34.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
- } from "./chunk-JG7ONJZ6.js";
15
+ } from "./chunk-Z3PCMYZO.js";
16
16
  export {
17
17
  DAEMON_CLI_USAGE,
18
18
  DaemonCore,
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-NM2MFLQ7.js";
7
- import "./chunk-JG7ONJZ6.js";
6
+ } from "./chunk-Q4XUZB34.js";
7
+ import "./chunk-Z3PCMYZO.js";
8
8
 
9
9
  // src/index.ts
10
10
  var parsedArgs = parseDaemonCliArgs(process.argv.slice(2));
@@ -12,7 +12,7 @@ if (!parsedArgs) {
12
12
  console.error(DAEMON_CLI_USAGE);
13
13
  process.exit(1);
14
14
  }
15
- var daemon = new DaemonCore(parsedArgs);
15
+ var daemon = new DaemonCore({ ...parsedArgs, localTrace: true });
16
16
  try {
17
17
  daemon.start();
18
18
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.44.2",
3
+ "version": "0.46.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"