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.
- package/CHANGELOG.md +25 -0
- package/README.md +5 -0
- package/dist/index.js +65172 -63908
- package/dist/mcp/mcp-server.js +333 -25
- package/docs/CONFIGURATION.md +144 -0
- package/package.json +5 -5
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -47754,7 +47754,11 @@ function truncateWithEllipsis(str, maxLength) {
|
|
|
47754
47754
|
return str.substring(0, maxLength) + "...";
|
|
47755
47755
|
}
|
|
47756
47756
|
function escapeCodeBlockContent(content) {
|
|
47757
|
-
|
|
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")}
|
|
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
|
|
57657
|
-
var sessionLog4 = createSessionLog(
|
|
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
|
|
57958
|
+
var log24 = createLogger("branch");
|
|
57663
57959
|
|
|
57664
57960
|
// src/operations/worktree/handler.ts
|
|
57665
|
-
var
|
|
57666
|
-
var sessionLog5 = createSessionLog(
|
|
57961
|
+
var log25 = createLogger("worktree");
|
|
57962
|
+
var sessionLog5 = createSessionLog(log25);
|
|
57667
57963
|
// src/operations/events/handler.ts
|
|
57668
|
-
var
|
|
57669
|
-
var sessionLog6 = createSessionLog(
|
|
57964
|
+
var log26 = createLogger("events");
|
|
57965
|
+
var sessionLog6 = createSessionLog(log26);
|
|
57670
57966
|
// src/operations/monitor/handler.ts
|
|
57671
|
-
var
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|