claude-threads 1.25.1 → 1.27.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.
@@ -47754,7 +47754,11 @@ function truncateWithEllipsis(str, maxLength) {
47754
47754
  return str.substring(0, maxLength) + "...";
47755
47755
  }
47756
47756
  function escapeCodeBlockContent(content) {
47757
- return content.replace(/```/g, "` ``");
47757
+ let result = content;
47758
+ while (result.includes("```")) {
47759
+ result = result.replace(/```/g, "` ``");
47760
+ }
47761
+ return result;
47758
47762
  }
47759
47763
  // src/operations/tool-formatters/registry.ts
47760
47764
  class ToolFormatterRegistry {
@@ -48553,6 +48557,15 @@ var fileToolsFormatter = {
48553
48557
  }
48554
48558
  };
48555
48559
  // src/operations/tool-formatters/bash-tools.ts
48560
+ var PERMISSION_COMMAND_MAX = 1500;
48561
+ function truncateAtCodePoint(text, max) {
48562
+ if (text.length <= max)
48563
+ return { text, truncated: false };
48564
+ const points = Array.from(text);
48565
+ if (points.length <= max)
48566
+ return { text, truncated: false };
48567
+ return { text: points.slice(0, max).join(""), truncated: true };
48568
+ }
48556
48569
  var bashToolFormatter = {
48557
48570
  toolNames: ["Bash"],
48558
48571
  format(toolName, input, options) {
@@ -48565,9 +48578,13 @@ var bashToolFormatter = {
48565
48578
  }
48566
48579
  const truncated = cmd.length > maxCommandLength;
48567
48580
  const displayCmd = cmd.substring(0, maxCommandLength);
48581
+ const permission = truncateAtCodePoint(cmd, PERMISSION_COMMAND_MAX);
48582
+ const permissionCmd = escapeCodeBlockContent(permission.text) + (permission.truncated ? `
48583
+ [... truncated]` : "");
48568
48584
  return {
48569
48585
  display: `\uD83D\uDCBB ${formatter.formatBold("Bash")} ${formatter.formatCode(displayCmd + (truncated ? "..." : ""))}`,
48570
- permissionText: `\uD83D\uDCBB ${formatter.formatBold("Bash")} ${formatter.formatCode(cmd.substring(0, 100) + (cmd.length >= 100 ? "..." : ""))}`,
48586
+ permissionText: `\uD83D\uDCBB ${formatter.formatBold("Bash")}
48587
+ ${formatter.formatCodeBlock(permissionCmd, "bash")}`,
48571
48588
  isDestructive: true
48572
48589
  };
48573
48590
  }
@@ -49632,6 +49649,13 @@ function convertMarkdownTablesToSlack(content) {
49632
49649
  `);
49633
49650
  });
49634
49651
  }
49652
+ var DCM_THREAD_PREFIX = "dcm:";
49653
+ function isDcmThreadId(threadId) {
49654
+ return !!threadId && threadId.startsWith(DCM_THREAD_PREFIX);
49655
+ }
49656
+ function resolvePostThreadId(threadId) {
49657
+ return isDcmThreadId(threadId) ? undefined : threadId;
49658
+ }
49635
49659
 
49636
49660
  // src/version.ts
49637
49661
  import { readFileSync, existsSync } from "fs";
@@ -51087,7 +51111,8 @@ class PromptExecutor extends BaseExecutor {
51087
51111
  return {
51088
51112
  pendingContextPrompt: null,
51089
51113
  pendingExistingWorktreePrompt: null,
51090
- pendingUpdatePrompt: null
51114
+ pendingUpdatePrompt: null,
51115
+ pendingRoutinePrompt: null
51091
51116
  };
51092
51117
  }
51093
51118
  getInitialState() {
@@ -51097,14 +51122,16 @@ class PromptExecutor extends BaseExecutor {
51097
51122
  return {
51098
51123
  pendingContextPrompt: this.state.pendingContextPrompt ? { ...this.state.pendingContextPrompt } : null,
51099
51124
  pendingExistingWorktreePrompt: this.state.pendingExistingWorktreePrompt ? { ...this.state.pendingExistingWorktreePrompt } : null,
51100
- pendingUpdatePrompt: this.state.pendingUpdatePrompt ? { ...this.state.pendingUpdatePrompt } : null
51125
+ pendingUpdatePrompt: this.state.pendingUpdatePrompt ? { ...this.state.pendingUpdatePrompt } : null,
51126
+ pendingRoutinePrompt: this.state.pendingRoutinePrompt ? { ...this.state.pendingRoutinePrompt } : null
51101
51127
  };
51102
51128
  }
51103
51129
  hydrateState(persisted) {
51104
51130
  this.state = {
51105
51131
  pendingContextPrompt: persisted.pendingContextPrompt ?? null,
51106
51132
  pendingExistingWorktreePrompt: persisted.pendingExistingWorktreePrompt ?? null,
51107
- pendingUpdatePrompt: persisted.pendingUpdatePrompt ?? null
51133
+ pendingUpdatePrompt: persisted.pendingUpdatePrompt ?? null,
51134
+ pendingRoutinePrompt: null
51108
51135
  };
51109
51136
  }
51110
51137
  setPendingContextPrompt(prompt) {
@@ -51234,6 +51261,30 @@ class PromptExecutor extends BaseExecutor {
51234
51261
  }
51235
51262
  return true;
51236
51263
  }
51264
+ setPendingRoutinePrompt(prompt) {
51265
+ this.state.pendingRoutinePrompt = prompt;
51266
+ }
51267
+ hasPendingRoutinePrompt() {
51268
+ return this.state.pendingRoutinePrompt !== null;
51269
+ }
51270
+ async handleRoutinePromptResponse(postId, approved, username, ctx) {
51271
+ if (!this.state.pendingRoutinePrompt)
51272
+ return false;
51273
+ if (this.state.pendingRoutinePrompt.postId !== postId)
51274
+ return false;
51275
+ const { parsed, requestedBy } = this.state.pendingRoutinePrompt;
51276
+ const statusMessage = approved ? `✅ ${ctx.formatter.formatBold(`Routine "${parsed.name}" confirmed`)} by ${ctx.formatter.formatUserMention(username)} — saving...` : `❌ ${ctx.formatter.formatBold(`Routine "${parsed.name}" discarded`)} by ${ctx.formatter.formatUserMention(username)}`;
51277
+ try {
51278
+ await ctx.platform.updatePost(postId, statusMessage);
51279
+ } catch (err) {
51280
+ ctx.logger.debug(`Failed to update routine prompt post: ${err}`);
51281
+ }
51282
+ this.state.pendingRoutinePrompt = null;
51283
+ if (this.events) {
51284
+ this.events.emit("routine-prompt:complete", { approved, parsed, requestedBy, postId });
51285
+ }
51286
+ return true;
51287
+ }
51237
51288
  async handleReaction(postId, emoji4, user, action, ctx) {
51238
51289
  ctx.logger.debug(`PromptExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, user=${user}, action=${action}`);
51239
51290
  if (action !== "added") {
@@ -51294,6 +51345,18 @@ class PromptExecutor extends BaseExecutor {
51294
51345
  ctx.logger.debug(`PromptExecutor: emoji ${emoji4} not valid for update prompt, ignoring`);
51295
51346
  return false;
51296
51347
  }
51348
+ if (this.state.pendingRoutinePrompt?.postId === postId) {
51349
+ if (isApprovalEmoji(emoji4)) {
51350
+ ctx.logger.debug(`Routine prompt reaction from @${user}: approve`);
51351
+ return this.handleRoutinePromptResponse(postId, true, user, ctx);
51352
+ }
51353
+ if (isDenialEmoji(emoji4)) {
51354
+ ctx.logger.debug(`Routine prompt reaction from @${user}: discard`);
51355
+ return this.handleRoutinePromptResponse(postId, false, user, ctx);
51356
+ }
51357
+ ctx.logger.debug(`PromptExecutor: emoji ${emoji4} not valid for routine prompt, ignoring`);
51358
+ return false;
51359
+ }
51297
51360
  ctx.logger.debug(`PromptExecutor: no pending prompt state matches postId=${postId.substring(0, 8)}`);
51298
51361
  return false;
51299
51362
  }
@@ -51910,6 +51973,9 @@ class MessageManager {
51910
51973
  clearPendingUpdatePrompt() {
51911
51974
  this.promptExecutor.clearPendingUpdatePrompt();
51912
51975
  }
51976
+ setPendingRoutinePrompt(prompt) {
51977
+ this.promptExecutor.setPendingRoutinePrompt(prompt);
51978
+ }
51913
51979
  setPendingBugReport(report) {
51914
51980
  this.bugReportExecutor.setPendingBugReport(report);
51915
51981
  }
@@ -52184,6 +52250,9 @@ import { resolve as resolve2, dirname as dirname2 } from "path";
52184
52250
  import { homedir } from "os";
52185
52251
 
52186
52252
  // node_modules/js-yaml/dist/js-yaml.mjs
52253
+ function getDefaultExportFromCjs(x) {
52254
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
52255
+ }
52187
52256
  var jsYaml = {};
52188
52257
  var loader = {};
52189
52258
  var common = {};
@@ -55287,6 +55356,7 @@ function requireJsYaml() {
55287
55356
  return jsYaml;
55288
55357
  }
55289
55358
  var jsYamlExports = requireJsYaml();
55359
+ var yaml = /* @__PURE__ */ getDefaultExportFromCjs(jsYamlExports);
55290
55360
 
55291
55361
  // src/config/index.ts
55292
55362
  var CONFIG_PATH = resolve2(homedir(), ".config", "claude-threads", "config.yaml");
@@ -56784,6 +56854,28 @@ var COMMAND_REGISTRY = [
56784
56854
  { name: "forget", description: "Remove one entry (by number or matching text), or all", args: "<n|text> | all" }
56785
56855
  ]
56786
56856
  },
56857
+ {
56858
+ command: "routine",
56859
+ description: "Create a scheduled routine from a natural-language request (confirmed with \uD83D\uDC4D before saving)",
56860
+ args: "<schedule, task>",
56861
+ category: "settings",
56862
+ audience: "user",
56863
+ claudeNotes: "User decisions, not yours"
56864
+ },
56865
+ {
56866
+ command: "routines",
56867
+ description: "List scheduled routines; pause/resume/delete/run manage them",
56868
+ args: "[pause|resume|delete|run <n>]",
56869
+ category: "settings",
56870
+ audience: "user",
56871
+ claudeNotes: "User decisions, not yours",
56872
+ subcommands: [
56873
+ { name: "pause", description: "Pause a routine", args: "<n>" },
56874
+ { name: "resume", description: "Resume a paused routine", args: "<n>" },
56875
+ { name: "delete", description: "Delete a routine", args: "<n>" },
56876
+ { name: "run", description: "Run a routine now, outside its schedule", args: "<n>" }
56877
+ ]
56878
+ },
56787
56879
  {
56788
56880
  command: "update",
56789
56881
  description: "Show auto-update status",
@@ -57058,6 +57150,30 @@ var handleMemory = async (ctx, args) => {
57058
57150
  await ctx.client.createPost(`⚠️ Usage: ${ctx.formatter.formatCode("!memory")} or ${ctx.formatter.formatCode("!memory forget <n|text>")} or ${ctx.formatter.formatCode("!memory forget all")}`, ctx.threadId);
57059
57151
  return { handled: true };
57060
57152
  };
57153
+ var handleRoutine = async (ctx, args) => {
57154
+ if (ctx.commandContext === "first-message") {
57155
+ return { handled: false };
57156
+ }
57157
+ if (!ctx.isAllowed) {
57158
+ return { handled: true };
57159
+ }
57160
+ if (!args?.trim()) {
57161
+ await ctx.client.createPost(`⚠️ Usage: ${ctx.formatter.formatCode("!routine every weekday at 9:00, <task>")}`, ctx.threadId);
57162
+ return { handled: true };
57163
+ }
57164
+ await ctx.sessionManager.createRoutine(ctx.threadId, args, ctx.username);
57165
+ return { handled: true };
57166
+ };
57167
+ var handleRoutines = async (ctx, args) => {
57168
+ if (ctx.commandContext === "first-message") {
57169
+ return { handled: false };
57170
+ }
57171
+ if (!ctx.isAllowed) {
57172
+ return { handled: true };
57173
+ }
57174
+ await ctx.sessionManager.manageRoutines(ctx.threadId, args, ctx.username);
57175
+ return { handled: true };
57176
+ };
57061
57177
  var handleCd = async (ctx, args) => {
57062
57178
  if (!args) {
57063
57179
  return { handled: false };
@@ -57249,6 +57365,8 @@ handlers.set("kick", handleKick);
57249
57365
  handlers.set("github-email", handleGitHubEmail);
57250
57366
  handlers.set("remember", handleRemember);
57251
57367
  handlers.set("memory", handleMemory);
57368
+ handlers.set("routine", handleRoutine);
57369
+ handlers.set("routines", handleRoutines);
57252
57370
  handlers.set("cd", handleCd);
57253
57371
  handlers.set("permissions", handlePermissions);
57254
57372
  handlers.set("mentions", handleMentions);
@@ -57638,6 +57756,7 @@ var log18 = createLogger("memory");
57638
57756
  // src/session/lifecycle.ts
57639
57757
  var log19 = createLogger("lifecycle");
57640
57758
  var sessionLog3 = createSessionLog(log19);
57759
+ var _inFlightSessionStarts = new Map;
57641
57760
  var CHAT_PLATFORM_PROMPT = generateChatPlatformPrompt();
57642
57761
  // src/update-notifier.ts
57643
57762
  var import_semver2 = __toESM(require_semver2(), 1);
@@ -57652,23 +57771,200 @@ var log20 = createLogger("gh-emails");
57652
57771
  var DEFAULT_CONFIG_DIR = join7(homedir5(), ".config", "claude-threads");
57653
57772
  var DEFAULT_FILE = join7(DEFAULT_CONFIG_DIR, "github-emails.yaml");
57654
57773
 
57774
+ // src/persistence/routines-store.ts
57775
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
57776
+ import { homedir as homedir6 } from "os";
57777
+ import { join as join8 } from "path";
57778
+ import { randomUUID as randomUUID2 } from "crypto";
57779
+
57780
+ // src/persistence/atomic-file.ts
57781
+ import { chmodSync as chmodSync2, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
57782
+
57783
+ class SerialQueue {
57784
+ tail = Promise.resolve();
57785
+ run(fn) {
57786
+ const next = this.tail.then(fn, fn);
57787
+ this.tail = next.catch(() => {
57788
+ return;
57789
+ });
57790
+ return next;
57791
+ }
57792
+ }
57793
+ function writeFileAtomic(file2, content) {
57794
+ const tempFile = `${file2}.tmp`;
57795
+ writeFileSync3(tempFile, content, { encoding: "utf-8", mode: 384 });
57796
+ renameSync2(tempFile, file2);
57797
+ chmodSync2(file2, 384);
57798
+ }
57799
+
57800
+ // src/persistence/routines-store.ts
57801
+ var log21 = createLogger("routines");
57802
+ var DEFAULT_CONFIG_DIR2 = join8(homedir6(), ".config", "claude-threads");
57803
+ var DEFAULT_FILE2 = join8(DEFAULT_CONFIG_DIR2, "routines.yaml");
57804
+ var STORE_VERSION = 1;
57805
+ var DEFAULT_MAX_ROUTINES = 10;
57806
+ var SCHEDULE_PRESETS = ["hourly", "daily", "weekdays", "weekly"];
57807
+ var TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
57808
+ function isValidTimezone(tz) {
57809
+ if (typeof tz !== "string" || !tz)
57810
+ return false;
57811
+ try {
57812
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
57813
+ return true;
57814
+ } catch {
57815
+ return false;
57816
+ }
57817
+ }
57818
+ function validateSchedule(schedule) {
57819
+ if (!SCHEDULE_PRESETS.includes(schedule.preset)) {
57820
+ return `unknown preset "${String(schedule.preset)}" (expected ${SCHEDULE_PRESETS.join("/")})`;
57821
+ }
57822
+ if (!isValidTimezone(schedule.timezone)) {
57823
+ return `invalid timezone "${String(schedule.timezone)}"`;
57824
+ }
57825
+ if (schedule.preset === "hourly") {
57826
+ return null;
57827
+ }
57828
+ if (!schedule.time || !TIME_RE.test(schedule.time)) {
57829
+ return `invalid time "${String(schedule.time)}" (expected HH:MM, 24h)`;
57830
+ }
57831
+ if (schedule.preset === "weekly") {
57832
+ const weekday = schedule.weekday;
57833
+ if (typeof weekday !== "number" || !Number.isInteger(weekday) || weekday < 1 || weekday > 7) {
57834
+ return `invalid weekday "${String(weekday)}" (expected 1=Mon … 7=Sun)`;
57835
+ }
57836
+ }
57837
+ return null;
57838
+ }
57839
+ class RoutinesStore {
57840
+ file;
57841
+ configDir;
57842
+ queue = new SerialQueue;
57843
+ constructor(filePath) {
57844
+ const effective = filePath ?? process.env.CLAUDE_THREADS_ROUTINES_PATH;
57845
+ if (effective) {
57846
+ this.file = effective;
57847
+ this.configDir = join8(effective, "..");
57848
+ } else {
57849
+ this.file = DEFAULT_FILE2;
57850
+ this.configDir = DEFAULT_CONFIG_DIR2;
57851
+ }
57852
+ if (!existsSync6(this.configDir)) {
57853
+ mkdirSync2(this.configDir, { recursive: true, mode: 448 });
57854
+ }
57855
+ }
57856
+ list(platformId) {
57857
+ return this.loadRaw().routines[platformId] ?? [];
57858
+ }
57859
+ get(platformId, id) {
57860
+ return this.list(platformId).find((r) => r.id === id);
57861
+ }
57862
+ add(platformId, routine, maxRoutines = DEFAULT_MAX_ROUTINES) {
57863
+ return this.runExclusive(() => {
57864
+ const scheduleError = validateSchedule(routine.schedule);
57865
+ if (scheduleError)
57866
+ return { ok: false, error: scheduleError };
57867
+ const name = routine.name.trim().slice(0, 80);
57868
+ const prompt = routine.prompt.trim().slice(0, 2000);
57869
+ if (!name || !prompt)
57870
+ return { ok: false, error: "name and prompt are required" };
57871
+ const data = this.loadRaw();
57872
+ const existing = data.routines[platformId] ?? [];
57873
+ if (existing.length >= maxRoutines) {
57874
+ return { ok: false, error: `routine limit reached (${maxRoutines}); delete one first` };
57875
+ }
57876
+ const full = {
57877
+ ...routine,
57878
+ name,
57879
+ prompt,
57880
+ id: randomUUID2().slice(0, 8),
57881
+ createdAt: new Date().toISOString(),
57882
+ enabled: true,
57883
+ consecutiveFailures: 0
57884
+ };
57885
+ data.routines[platformId] = [...existing, full];
57886
+ this.writeAtomic(data);
57887
+ log21.info(`Routine "${full.name}" created on ${platformId} by @${full.createdBy}`);
57888
+ return { ok: true, routine: full };
57889
+ });
57890
+ }
57891
+ update(platformId, id, patch) {
57892
+ return this.runExclusive(() => {
57893
+ const data = this.loadRaw();
57894
+ const routines = data.routines[platformId] ?? [];
57895
+ const idx = routines.findIndex((r) => r.id === id);
57896
+ if (idx < 0)
57897
+ return;
57898
+ routines[idx] = { ...routines[idx], ...patch };
57899
+ this.writeAtomic(data);
57900
+ return routines[idx];
57901
+ });
57902
+ }
57903
+ remove(platformId, id) {
57904
+ return this.runExclusive(() => {
57905
+ const data = this.loadRaw();
57906
+ const routines = data.routines[platformId] ?? [];
57907
+ const idx = routines.findIndex((r) => r.id === id);
57908
+ if (idx < 0)
57909
+ return;
57910
+ const [removed] = routines.splice(idx, 1);
57911
+ if (routines.length === 0)
57912
+ delete data.routines[platformId];
57913
+ this.writeAtomic(data);
57914
+ log21.info(`Routine "${removed.name}" removed from ${platformId}`);
57915
+ return removed;
57916
+ });
57917
+ }
57918
+ runExclusive(fn) {
57919
+ return this.queue.run(fn);
57920
+ }
57921
+ loadRaw() {
57922
+ if (!existsSync6(this.file)) {
57923
+ return { version: STORE_VERSION, routines: {} };
57924
+ }
57925
+ try {
57926
+ const parsed = yaml.load(readFileSync5(this.file, "utf-8"));
57927
+ if (!parsed || typeof parsed !== "object") {
57928
+ return { version: STORE_VERSION, routines: {} };
57929
+ }
57930
+ const routines = parsed.routines && typeof parsed.routines === "object" ? parsed.routines : {};
57931
+ for (const list of Object.values(routines)) {
57932
+ for (const r of list) {
57933
+ r.enabled = r.enabled ?? true;
57934
+ r.consecutiveFailures = r.consecutiveFailures ?? 0;
57935
+ }
57936
+ }
57937
+ return { version: parsed.version ?? STORE_VERSION, routines };
57938
+ } catch (err) {
57939
+ log21.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
57940
+ return { version: STORE_VERSION, routines: {} };
57941
+ }
57942
+ }
57943
+ writeAtomic(data) {
57944
+ writeFileAtomic(this.file, yaml.dump(data, { sortKeys: true, lineWidth: -1 }));
57945
+ }
57946
+ }
57947
+
57948
+ // src/routines/parser.ts
57949
+ var log22 = createLogger("routines");
57950
+
57655
57951
  // src/operations/commands/handler.ts
57656
- var log21 = createLogger("commands");
57657
- var sessionLog4 = createSessionLog(log21);
57952
+ var log23 = createLogger("commands");
57953
+ var sessionLog4 = createSessionLog(log23);
57658
57954
  // src/operations/suggestions/branch.ts
57659
57955
  import { exec as exec2 } from "child_process";
57660
57956
  import { promisify as promisify2 } from "util";
57661
57957
  var execAsync2 = promisify2(exec2);
57662
- var log22 = createLogger("branch");
57958
+ var log24 = createLogger("branch");
57663
57959
 
57664
57960
  // src/operations/worktree/handler.ts
57665
- var log23 = createLogger("worktree");
57666
- var sessionLog5 = createSessionLog(log23);
57961
+ var log25 = createLogger("worktree");
57962
+ var sessionLog5 = createSessionLog(log25);
57667
57963
  // src/operations/events/handler.ts
57668
- var log24 = createLogger("events");
57669
- var sessionLog6 = createSessionLog(log24);
57964
+ var log26 = createLogger("events");
57965
+ var sessionLog6 = createSessionLog(log26);
57670
57966
  // src/operations/monitor/handler.ts
57671
- var log25 = createLogger("monitor");
57967
+ var log27 = createLogger("monitor");
57672
57968
  var DEFAULT_INTERVAL_MS = 60 * 1000;
57673
57969
  // src/utils/websocket.ts
57674
57970
  var WS;
@@ -57750,7 +58046,7 @@ ${code}
57750
58046
 
57751
58047
  // src/platform/mattermost/upload.ts
57752
58048
  import { readFile } from "fs/promises";
57753
- var log26 = createLogger("mm-upload");
58049
+ var log28 = createLogger("mm-upload");
57754
58050
  async function uploadFileMattermost(args) {
57755
58051
  const { url: url2, token, channelId, threadId, filePath, filename, caption } = args;
57756
58052
  const buffer = await readFile(filePath);
@@ -57758,7 +58054,7 @@ async function uploadFileMattermost(args) {
57758
58054
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
57759
58055
  const formData = new FormData;
57760
58056
  formData.append("files", new Blob([arrayBuffer]), filename);
57761
- log26.debug(`POST /files (${buffer.length} bytes, ${filename})`);
58057
+ log28.debug(`POST /files (${buffer.length} bytes, ${filename})`);
57762
58058
  const uploadResponse = await fetch(uploadUrl, {
57763
58059
  method: "POST",
57764
58060
  headers: {
@@ -57779,10 +58075,10 @@ async function uploadFileMattermost(args) {
57779
58075
  const postBody = {
57780
58076
  channel_id: channelId,
57781
58077
  message: caption ?? "",
57782
- root_id: threadId,
58078
+ root_id: resolvePostThreadId(threadId),
57783
58079
  file_ids: [fileInfo.id]
57784
58080
  };
57785
- log26.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
58081
+ log28.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
57786
58082
  const postResponse = await fetch(postUrl, {
57787
58083
  method: "POST",
57788
58084
  headers: {
@@ -57835,7 +58131,7 @@ async function createPost(config3, channelId, message, rootId) {
57835
58131
  return mattermostApi(config3, "POST", "/posts", {
57836
58132
  channel_id: channelId,
57837
58133
  message,
57838
- root_id: rootId
58134
+ root_id: resolvePostThreadId(rootId)
57839
58135
  });
57840
58136
  }
57841
58137
  async function updatePostRaw(config3, postId, message) {
@@ -58276,7 +58572,7 @@ ${code}
58276
58572
 
58277
58573
  // src/platform/slack/upload.ts
58278
58574
  import { readFile as readFile2 } from "fs/promises";
58279
- var log27 = createLogger("slack-upload");
58575
+ var log29 = createLogger("slack-upload");
58280
58576
  var DEFAULT_API_URL = "https://slack.com/api";
58281
58577
  async function uploadFileSlack(args) {
58282
58578
  const { botToken, channelId, threadTs, filePath, filename, caption } = args;
@@ -58284,7 +58580,7 @@ async function uploadFileSlack(args) {
58284
58580
  const buffer = await readFile2(filePath);
58285
58581
  const params = new URLSearchParams({ filename, length: String(buffer.length) });
58286
58582
  const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
58287
- log27.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
58583
+ log29.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
58288
58584
  const step1Response = await fetch(step1Url, {
58289
58585
  method: "GET",
58290
58586
  headers: {
@@ -58302,7 +58598,7 @@ async function uploadFileSlack(args) {
58302
58598
  const uploadUrl = step1Data.upload_url;
58303
58599
  const fileId = step1Data.file_id;
58304
58600
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
58305
- log27.debug(`POST <upload_url>`);
58601
+ log29.debug(`POST <upload_url>`);
58306
58602
  const step2Response = await fetch(uploadUrl, {
58307
58603
  method: "POST",
58308
58604
  headers: {
@@ -58317,12 +58613,12 @@ async function uploadFileSlack(args) {
58317
58613
  const step3Body = {
58318
58614
  files: [{ id: fileId, title: caption ?? filename }],
58319
58615
  channel_id: channelId,
58320
- thread_ts: threadTs
58616
+ thread_ts: resolvePostThreadId(threadTs)
58321
58617
  };
58322
58618
  if (caption !== undefined) {
58323
58619
  step3Body.initial_comment = caption;
58324
58620
  }
58325
- log27.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
58621
+ log29.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
58326
58622
  const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
58327
58623
  method: "POST",
58328
58624
  headers: {
@@ -58340,7 +58636,7 @@ async function uploadFileSlack(args) {
58340
58636
  throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
58341
58637
  }
58342
58638
  if (!step3Data.ts) {
58343
- log27.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
58639
+ log29.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
58344
58640
  }
58345
58641
  return { fileId, postId: step3Data.ts ?? fileId };
58346
58642
  }
@@ -58416,7 +58712,7 @@ class SlackMcpPlatformApi {
58416
58712
  const response = await slackApi("chat.postMessage", this.config.botToken, {
58417
58713
  channel: this.config.channelId,
58418
58714
  text: message,
58419
- thread_ts: threadTs || this.config.threadTs,
58715
+ thread_ts: resolvePostThreadId(threadTs || this.config.threadTs),
58420
58716
  mrkdwn: true
58421
58717
  });
58422
58718
  const messageTs = response.ts;
@@ -59360,6 +59656,12 @@ async function resolveLatestThreadPost(cfg) {
59360
59656
  if (!cfg.sessionThreadId) {
59361
59657
  return { ok: false, reason: "no session thread to react in — pass a permalink URL instead" };
59362
59658
  }
59659
+ if (isDcmThreadId(cfg.sessionThreadId)) {
59660
+ return {
59661
+ ok: false,
59662
+ reason: "this session runs in direct channel mode (no thread of its own) — pass a permalink URL to the target message instead"
59663
+ };
59664
+ }
59363
59665
  let thread;
59364
59666
  try {
59365
59667
  thread = await cfg.api.readThread(cfg.sessionThreadId);
@@ -59461,6 +59763,12 @@ async function handleListThreadWith(args, cfg) {
59461
59763
  reason: "no session thread to read — pass a permalink URL instead"
59462
59764
  };
59463
59765
  }
59766
+ if (isDcmThreadId(cfg.sessionThreadId)) {
59767
+ return {
59768
+ ok: false,
59769
+ reason: "this session runs in direct channel mode (no thread of its own) — use read_channel_history, or pass a permalink URL"
59770
+ };
59771
+ }
59464
59772
  rootId = cfg.sessionThreadId;
59465
59773
  }
59466
59774
  const limit = clampThreadLimit(args.max_messages);