claude-threads 1.2.1 → 1.3.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 +16 -0
- package/bin/claude-threads-daemon +11 -1
- package/dist/index.js +356 -107
- package/dist/mcp/permission-server.js +13 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-01-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Dynamic slash command passthrough** - Unknown `!commands` are now checked against Claude CLI's available slash commands and passed through automatically (#229)
|
|
12
|
+
- Captures `slash_commands` from Claude CLI's `init` event
|
|
13
|
+
- `!foo` passes through to `/foo` if it's an available slash command
|
|
14
|
+
- **`!plugin` command** - New command for managing Claude Code plugins (#229)
|
|
15
|
+
- `!plugin list` - Shows installed plugins
|
|
16
|
+
- `!plugin install <name>` - Installs a plugin and restarts Claude CLI
|
|
17
|
+
- `!plugin uninstall <name>` - Uninstalls a plugin and restarts Claude CLI
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Package manager detection for updates** - Updates now use the same package manager (bun/npm) that was used to install claude-threads (#230)
|
|
21
|
+
- Prevents duplicate global installations when updating
|
|
22
|
+
- Respects `BUN_INSTALL` env var for custom bun install locations
|
|
23
|
+
|
|
8
24
|
## [1.2.1] - 2026-01-17
|
|
9
25
|
|
|
10
26
|
### Fixed
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
# - 0: Clean exit, don't restart
|
|
10
10
|
# - 1+: Error, optional restart based on flags
|
|
11
11
|
#
|
|
12
|
+
# PLATFORM SUPPORT:
|
|
13
|
+
# - Linux: Full support
|
|
14
|
+
# - macOS: Full support
|
|
15
|
+
# - Windows: Requires Git Bash, WSL, or similar bash environment
|
|
16
|
+
# Without bash, use claude-threads directly (no auto-restart)
|
|
17
|
+
#
|
|
12
18
|
# Usage:
|
|
13
19
|
# claude-threads-daemon [options]
|
|
14
20
|
# claude-threads-daemon --restart-on-error # Also restart on errors
|
|
@@ -32,9 +38,13 @@ VERBOSE=false
|
|
|
32
38
|
|
|
33
39
|
# Path to claude-threads binary (set by index.ts when spawning daemon)
|
|
34
40
|
# Falls back to global 'claude-threads' command if not set
|
|
35
|
-
# If CLAUDE_THREADS_BIN is a .js file, run it with node
|
|
41
|
+
# If CLAUDE_THREADS_BIN is a .js file, run it with node (matches the shebang)
|
|
36
42
|
if [ -n "$CLAUDE_THREADS_BIN" ]; then
|
|
37
43
|
if [[ "$CLAUDE_THREADS_BIN" == *.js ]]; then
|
|
44
|
+
if ! command -v node &> /dev/null; then
|
|
45
|
+
echo "[daemon] Error: node not found in PATH" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
38
48
|
CLAUDE_THREADS_CMD="node $CLAUDE_THREADS_BIN"
|
|
39
49
|
else
|
|
40
50
|
CLAUDE_THREADS_CMD="$CLAUDE_THREADS_BIN"
|
package/dist/index.js
CHANGED
|
@@ -16227,7 +16227,7 @@ var require_react_reconciler_development = __commonJS((exports, module) => {
|
|
|
16227
16227
|
return hook.checkDCE ? true : false;
|
|
16228
16228
|
}
|
|
16229
16229
|
function setIsStrictModeForDevtools(newIsStrictMode) {
|
|
16230
|
-
typeof
|
|
16230
|
+
typeof log30 === "function" && unstable_setDisableYieldValue2(newIsStrictMode);
|
|
16231
16231
|
if (injectedHook && typeof injectedHook.setStrictMode === "function")
|
|
16232
16232
|
try {
|
|
16233
16233
|
injectedHook.setStrictMode(rendererID, newIsStrictMode);
|
|
@@ -24311,7 +24311,7 @@ Check the render method of %s.`, getComponentNameFromFiber(current) || "Unknown"
|
|
|
24311
24311
|
var fiberStack = [];
|
|
24312
24312
|
var index$jscomp$0 = -1, emptyContextObject = {};
|
|
24313
24313
|
Object.freeze(emptyContextObject);
|
|
24314
|
-
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority,
|
|
24314
|
+
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback, log$1 = Math.log, LN2 = Math.LN2, nextTransitionUpdateLane = 256, nextTransitionDeferredLane = 262144, nextRetryLane = 4194304, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, shouldYield = Scheduler.unstable_shouldYield, requestPaint = Scheduler.unstable_requestPaint, now$1 = Scheduler.unstable_now, ImmediatePriority = Scheduler.unstable_ImmediatePriority, UserBlockingPriority = Scheduler.unstable_UserBlockingPriority, NormalPriority$1 = Scheduler.unstable_NormalPriority, IdlePriority = Scheduler.unstable_IdlePriority, log30 = Scheduler.log, unstable_setDisableYieldValue2 = Scheduler.unstable_setDisableYieldValue, rendererID = null, injectedHook = null, hasLoggedError = false, isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined", lastResetTime = 0;
|
|
24315
24315
|
if (typeof performance === "object" && typeof performance.now === "function") {
|
|
24316
24316
|
var localPerformance = performance;
|
|
24317
24317
|
var getCurrentTime = function() {
|
|
@@ -54370,6 +54370,19 @@ var COMMAND_REGISTRY = [
|
|
|
54370
54370
|
claudeCanExecute: true,
|
|
54371
54371
|
returnsResultToClaude: false
|
|
54372
54372
|
},
|
|
54373
|
+
{
|
|
54374
|
+
command: "plugin",
|
|
54375
|
+
description: "Manage Claude Code plugins",
|
|
54376
|
+
args: "<subcommand> [name]",
|
|
54377
|
+
category: "system",
|
|
54378
|
+
audience: "user",
|
|
54379
|
+
claudeNotes: "User manages plugins themselves",
|
|
54380
|
+
subcommands: [
|
|
54381
|
+
{ name: "list", description: "List installed plugins" },
|
|
54382
|
+
{ name: "install", description: "Install a plugin (restarts Claude)", args: "<name>" },
|
|
54383
|
+
{ name: "uninstall", description: "Uninstall a plugin (restarts Claude)", args: "<name>" }
|
|
54384
|
+
]
|
|
54385
|
+
},
|
|
54373
54386
|
{
|
|
54374
54387
|
command: "context",
|
|
54375
54388
|
description: "Show context usage",
|
|
@@ -54443,13 +54456,22 @@ var COMMAND_PATTERNS = [
|
|
|
54443
54456
|
["context", /^!context\s*$/i],
|
|
54444
54457
|
["cost", /^!cost\s*$/i],
|
|
54445
54458
|
["compact", /^!compact\s*$/i],
|
|
54459
|
+
["plugin", /^!plugin(?:\s+(.+))?$/i],
|
|
54446
54460
|
["kill", /^!kill\s*$/i],
|
|
54447
|
-
["bug", /^!bug(?:\s+(.+))?$/i]
|
|
54461
|
+
["bug", /^!bug(?:\s+(.+))?$/i],
|
|
54462
|
+
["_dynamic", /^!([a-z][-a-z0-9]*)(?:\s+(.+))?$/i]
|
|
54448
54463
|
];
|
|
54449
54464
|
function parseCommand(text) {
|
|
54450
54465
|
for (const [command, pattern] of COMMAND_PATTERNS) {
|
|
54451
54466
|
const match = text.match(pattern);
|
|
54452
54467
|
if (match) {
|
|
54468
|
+
if (command === "_dynamic") {
|
|
54469
|
+
return {
|
|
54470
|
+
command: match[1].toLowerCase(),
|
|
54471
|
+
args: match[2]?.trim(),
|
|
54472
|
+
match: match[0]
|
|
54473
|
+
};
|
|
54474
|
+
}
|
|
54453
54475
|
return {
|
|
54454
54476
|
command,
|
|
54455
54477
|
args: match[1]?.trim(),
|
|
@@ -65705,6 +65727,10 @@ function handleEventPreProcessing(session, event, ctx) {
|
|
|
65705
65727
|
}
|
|
65706
65728
|
if (event.type === "system") {
|
|
65707
65729
|
const e = event;
|
|
65730
|
+
if (e.subtype === "init" && e.slash_commands && Array.isArray(e.slash_commands)) {
|
|
65731
|
+
session.availableSlashCommands = new Set(e.slash_commands.map((cmd) => cmd.startsWith("/") ? cmd.slice(1) : cmd));
|
|
65732
|
+
sessionLog4(session).info(`Captured ${session.availableSlashCommands.size} slash commands from init: ${[...session.availableSlashCommands].join(", ")}`);
|
|
65733
|
+
}
|
|
65708
65734
|
if (e.subtype === "status" && e.status === "compacting") {
|
|
65709
65735
|
handleCompactionStart(session, ctx);
|
|
65710
65736
|
}
|
|
@@ -67024,6 +67050,114 @@ class SessionMonitor {
|
|
|
67024
67050
|
}
|
|
67025
67051
|
}
|
|
67026
67052
|
}
|
|
67053
|
+
// src/operations/plugin/handler.ts
|
|
67054
|
+
import { spawn as spawn7 } from "child_process";
|
|
67055
|
+
var log27 = createLogger("plugin");
|
|
67056
|
+
var sessionLog7 = createSessionLog(log27);
|
|
67057
|
+
async function runPluginCommand(args, cwd, timeout2 = 60000) {
|
|
67058
|
+
return new Promise((resolve6) => {
|
|
67059
|
+
const claudePath = process.env.CLAUDE_PATH || "claude";
|
|
67060
|
+
const proc = spawn7(claudePath, ["plugin", ...args], {
|
|
67061
|
+
cwd,
|
|
67062
|
+
timeout: timeout2
|
|
67063
|
+
});
|
|
67064
|
+
let stdout = "";
|
|
67065
|
+
let stderr = "";
|
|
67066
|
+
proc.stdout.on("data", (data) => {
|
|
67067
|
+
stdout += data.toString();
|
|
67068
|
+
});
|
|
67069
|
+
proc.stderr.on("data", (data) => {
|
|
67070
|
+
stderr += data.toString();
|
|
67071
|
+
});
|
|
67072
|
+
proc.on("close", (code) => {
|
|
67073
|
+
resolve6({ stdout, stderr, exitCode: code ?? 1 });
|
|
67074
|
+
});
|
|
67075
|
+
proc.on("error", (err) => {
|
|
67076
|
+
resolve6({ stdout, stderr, exitCode: 1 });
|
|
67077
|
+
log27.error(`Plugin command error: ${err.message}`);
|
|
67078
|
+
});
|
|
67079
|
+
});
|
|
67080
|
+
}
|
|
67081
|
+
async function handlePluginList(session) {
|
|
67082
|
+
const formatter = session.platform.getFormatter();
|
|
67083
|
+
await post(session, "info", `\uD83D\uDCE6 Listing installed plugins...`);
|
|
67084
|
+
const result = await runPluginCommand(["list"], session.workingDir);
|
|
67085
|
+
if (result.exitCode !== 0) {
|
|
67086
|
+
await postError(session, `Failed to list plugins:
|
|
67087
|
+
${formatter.formatCodeBlock(result.stderr || result.stdout, "text")}`);
|
|
67088
|
+
return;
|
|
67089
|
+
}
|
|
67090
|
+
const output = result.stdout.trim() || "No plugins installed";
|
|
67091
|
+
await post(session, "info", `${formatter.formatBold("Installed plugins:")}
|
|
67092
|
+
${formatter.formatCodeBlock(output, "text")}`);
|
|
67093
|
+
sessionLog7(session).info(`Listed plugins: ${output.substring(0, 100)}...`);
|
|
67094
|
+
}
|
|
67095
|
+
async function handlePluginInstall(session, pluginName, username, ctx) {
|
|
67096
|
+
const formatter = session.platform.getFormatter();
|
|
67097
|
+
await post(session, "info", `\uD83D\uDCE6 Installing plugin: ${formatter.formatCode(pluginName)}...`);
|
|
67098
|
+
sessionLog7(session).info(`Installing plugin: ${pluginName} (requested by @${username})`);
|
|
67099
|
+
session.threadLogger?.logCommand("plugin install", pluginName, username);
|
|
67100
|
+
const result = await runPluginCommand(["install", pluginName], session.workingDir);
|
|
67101
|
+
if (result.exitCode !== 0) {
|
|
67102
|
+
const errorMsg = result.stderr || result.stdout || "Unknown error";
|
|
67103
|
+
await postError(session, `Failed to install plugin ${formatter.formatCode(pluginName)}:
|
|
67104
|
+
${formatter.formatCodeBlock(errorMsg, "text")}`);
|
|
67105
|
+
sessionLog7(session).error(`Failed to install plugin ${pluginName}: ${errorMsg}`);
|
|
67106
|
+
return;
|
|
67107
|
+
}
|
|
67108
|
+
await post(session, "success", `✅ Plugin installed: ${formatter.formatCode(pluginName)}
|
|
67109
|
+
\uD83D\uDD04 Restarting Claude to load plugin...`);
|
|
67110
|
+
const cliOptions = {
|
|
67111
|
+
workingDir: session.workingDir,
|
|
67112
|
+
threadId: session.threadId,
|
|
67113
|
+
skipPermissions: ctx.config.skipPermissions || !session.forceInteractivePermissions,
|
|
67114
|
+
sessionId: session.claudeSessionId,
|
|
67115
|
+
resume: true,
|
|
67116
|
+
chrome: ctx.config.chromeEnabled,
|
|
67117
|
+
platformConfig: session.platform.getMcpConfig(),
|
|
67118
|
+
logSessionId: session.sessionId,
|
|
67119
|
+
permissionTimeoutMs: ctx.config.permissionTimeoutMs
|
|
67120
|
+
};
|
|
67121
|
+
const success = await restartClaudeSession(session, cliOptions, ctx, `Plugin installation: ${pluginName}`);
|
|
67122
|
+
if (success) {
|
|
67123
|
+
sessionLog7(session).info(`Claude restarted after installing plugin: ${pluginName}`);
|
|
67124
|
+
} else {
|
|
67125
|
+
await postError(session, `Plugin installed but failed to restart Claude. Try ${formatter.formatCode("!cd .")} to manually restart.`);
|
|
67126
|
+
}
|
|
67127
|
+
}
|
|
67128
|
+
async function handlePluginUninstall(session, pluginName, username, ctx) {
|
|
67129
|
+
const formatter = session.platform.getFormatter();
|
|
67130
|
+
await post(session, "info", `\uD83D\uDDD1️ Uninstalling plugin: ${formatter.formatCode(pluginName)}...`);
|
|
67131
|
+
sessionLog7(session).info(`Uninstalling plugin: ${pluginName} (requested by @${username})`);
|
|
67132
|
+
session.threadLogger?.logCommand("plugin uninstall", pluginName, username);
|
|
67133
|
+
const result = await runPluginCommand(["uninstall", pluginName], session.workingDir);
|
|
67134
|
+
if (result.exitCode !== 0) {
|
|
67135
|
+
const errorMsg = result.stderr || result.stdout || "Unknown error";
|
|
67136
|
+
await postError(session, `Failed to uninstall plugin ${formatter.formatCode(pluginName)}:
|
|
67137
|
+
${formatter.formatCodeBlock(errorMsg, "text")}`);
|
|
67138
|
+
sessionLog7(session).error(`Failed to uninstall plugin ${pluginName}: ${errorMsg}`);
|
|
67139
|
+
return;
|
|
67140
|
+
}
|
|
67141
|
+
await post(session, "success", `✅ Plugin uninstalled: ${formatter.formatCode(pluginName)}
|
|
67142
|
+
\uD83D\uDD04 Restarting Claude...`);
|
|
67143
|
+
const cliOptions = {
|
|
67144
|
+
workingDir: session.workingDir,
|
|
67145
|
+
threadId: session.threadId,
|
|
67146
|
+
skipPermissions: ctx.config.skipPermissions || !session.forceInteractivePermissions,
|
|
67147
|
+
sessionId: session.claudeSessionId,
|
|
67148
|
+
resume: true,
|
|
67149
|
+
chrome: ctx.config.chromeEnabled,
|
|
67150
|
+
platformConfig: session.platform.getMcpConfig(),
|
|
67151
|
+
logSessionId: session.sessionId,
|
|
67152
|
+
permissionTimeoutMs: ctx.config.permissionTimeoutMs
|
|
67153
|
+
};
|
|
67154
|
+
const success = await restartClaudeSession(session, cliOptions, ctx, `Plugin uninstallation: ${pluginName}`);
|
|
67155
|
+
if (success) {
|
|
67156
|
+
sessionLog7(session).info(`Claude restarted after uninstalling plugin: ${pluginName}`);
|
|
67157
|
+
} else {
|
|
67158
|
+
await postError(session, `Plugin uninstalled but failed to restart Claude. Try ${formatter.formatCode("!cd .")} to manually restart.`);
|
|
67159
|
+
}
|
|
67160
|
+
}
|
|
67027
67161
|
// src/session/registry.ts
|
|
67028
67162
|
class SessionRegistry {
|
|
67029
67163
|
sessions = new Map;
|
|
@@ -67138,7 +67272,7 @@ class SessionRegistry {
|
|
|
67138
67272
|
}
|
|
67139
67273
|
|
|
67140
67274
|
// src/session/manager.ts
|
|
67141
|
-
var
|
|
67275
|
+
var log28 = createLogger("manager");
|
|
67142
67276
|
|
|
67143
67277
|
class SessionManager extends EventEmitter4 {
|
|
67144
67278
|
platforms = new Map;
|
|
@@ -67203,7 +67337,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67203
67337
|
markNeedsBump(platformId);
|
|
67204
67338
|
this.updateStickyMessage();
|
|
67205
67339
|
});
|
|
67206
|
-
|
|
67340
|
+
log28.info(`\uD83D\uDCE1 Platform "${platformId}" registered`);
|
|
67207
67341
|
}
|
|
67208
67342
|
removePlatform(platformId) {
|
|
67209
67343
|
this.platforms.delete(platformId);
|
|
@@ -67219,7 +67353,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67219
67353
|
if (users) {
|
|
67220
67354
|
users.add(sessionId);
|
|
67221
67355
|
}
|
|
67222
|
-
|
|
67356
|
+
log28.debug(`Registered session ${sessionId.substring(0, 20)} as worktree user for ${worktreePath}`);
|
|
67223
67357
|
}
|
|
67224
67358
|
unregisterWorktreeUser(worktreePath, sessionId) {
|
|
67225
67359
|
const users = this.worktreeUsers.get(worktreePath);
|
|
@@ -67366,7 +67500,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67366
67500
|
return false;
|
|
67367
67501
|
}
|
|
67368
67502
|
const shortId = persistedSession.threadId.substring(0, 8);
|
|
67369
|
-
|
|
67503
|
+
log28.info(`\uD83D\uDD04 Resuming session ${shortId}... via emoji reaction by @${username}`);
|
|
67370
67504
|
await resumeSession(persistedSession, this.getContext());
|
|
67371
67505
|
return true;
|
|
67372
67506
|
}
|
|
@@ -67396,7 +67530,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67396
67530
|
}
|
|
67397
67531
|
if (session.lastError?.postId === postId && isBugReportEmoji(emojiName)) {
|
|
67398
67532
|
if (session.startedBy === username || session.platform.isUserAllowed(username) || session.sessionAllowedUsers.has(username)) {
|
|
67399
|
-
|
|
67533
|
+
log28.info(`\uD83D\uDC1B @${username} triggered bug report from error reaction`);
|
|
67400
67534
|
await reportBug(session, undefined, username, this.getContext(), session.lastError);
|
|
67401
67535
|
return;
|
|
67402
67536
|
}
|
|
@@ -67556,11 +67690,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
67556
67690
|
}
|
|
67557
67691
|
}
|
|
67558
67692
|
if (sessionsToKill.length === 0) {
|
|
67559
|
-
|
|
67693
|
+
log28.info(`No active sessions to pause for platform ${platformId}`);
|
|
67560
67694
|
await this.updateStickyMessage();
|
|
67561
67695
|
return;
|
|
67562
67696
|
}
|
|
67563
|
-
|
|
67697
|
+
log28.info(`⏸️ Pausing ${sessionsToKill.length} session(s) for platform ${platformId}`);
|
|
67564
67698
|
for (const session of sessionsToKill) {
|
|
67565
67699
|
try {
|
|
67566
67700
|
const fmt = session.platform.getFormatter();
|
|
@@ -67576,9 +67710,9 @@ class SessionManager extends EventEmitter4 {
|
|
|
67576
67710
|
session.claude.kill();
|
|
67577
67711
|
this.registry.unregister(session.sessionId);
|
|
67578
67712
|
this.emitSessionRemove(session.sessionId);
|
|
67579
|
-
|
|
67713
|
+
log28.info(`⏸️ Paused session ${session.threadId.substring(0, 8)}`);
|
|
67580
67714
|
} catch (err) {
|
|
67581
|
-
|
|
67715
|
+
log28.warn(`Failed to pause session ${session.threadId}: ${err}`);
|
|
67582
67716
|
}
|
|
67583
67717
|
}
|
|
67584
67718
|
for (const session of sessionsToKill) {
|
|
@@ -67599,17 +67733,17 @@ class SessionManager extends EventEmitter4 {
|
|
|
67599
67733
|
sessionsToResume.push(state);
|
|
67600
67734
|
}
|
|
67601
67735
|
if (sessionsToResume.length === 0) {
|
|
67602
|
-
|
|
67736
|
+
log28.info(`No paused sessions to resume for platform ${platformId}`);
|
|
67603
67737
|
await this.updateStickyMessage();
|
|
67604
67738
|
return;
|
|
67605
67739
|
}
|
|
67606
|
-
|
|
67740
|
+
log28.info(`▶️ Resuming ${sessionsToResume.length} paused session(s) for platform ${platformId}`);
|
|
67607
67741
|
for (const state of sessionsToResume) {
|
|
67608
67742
|
try {
|
|
67609
67743
|
await resumeSession(state, this.getContext());
|
|
67610
|
-
|
|
67744
|
+
log28.info(`▶️ Resumed session ${state.threadId.substring(0, 8)}`);
|
|
67611
67745
|
} catch (err) {
|
|
67612
|
-
|
|
67746
|
+
log28.warn(`Failed to resume session ${state.threadId}: ${err}`);
|
|
67613
67747
|
}
|
|
67614
67748
|
}
|
|
67615
67749
|
await this.updateStickyMessage();
|
|
@@ -67621,14 +67755,14 @@ class SessionManager extends EventEmitter4 {
|
|
|
67621
67755
|
const sessionTimeoutMs = this.limits.sessionTimeoutMinutes * 60 * 1000;
|
|
67622
67756
|
const staleIds = this.sessionStore.cleanStale(sessionTimeoutMs * 2);
|
|
67623
67757
|
if (staleIds.length > 0) {
|
|
67624
|
-
|
|
67758
|
+
log28.info(`\uD83E\uDDF9 Soft-deleted ${staleIds.length} stale session(s) (kept for history)`);
|
|
67625
67759
|
}
|
|
67626
67760
|
const removedCount = this.sessionStore.cleanHistory();
|
|
67627
67761
|
if (removedCount > 0) {
|
|
67628
|
-
|
|
67762
|
+
log28.info(`\uD83D\uDDD1️ Permanently removed ${removedCount} old session(s) from history`);
|
|
67629
67763
|
}
|
|
67630
67764
|
const persisted = this.sessionStore.load();
|
|
67631
|
-
|
|
67765
|
+
log28.info(`\uD83D\uDCC2 Loaded ${persisted.size} session(s) from persistence`);
|
|
67632
67766
|
const excludePostIdsByPlatform = new Map;
|
|
67633
67767
|
for (const session of persisted.values()) {
|
|
67634
67768
|
const platformId = session.platformId;
|
|
@@ -67648,10 +67782,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
67648
67782
|
const excludePostIds = excludePostIdsByPlatform.get(platform.platformId);
|
|
67649
67783
|
platform.getBotUser().then((botUser) => {
|
|
67650
67784
|
cleanupOldStickyMessages(platform, botUser.id, true, excludePostIds).catch((err) => {
|
|
67651
|
-
|
|
67785
|
+
log28.warn(`Failed to cleanup old sticky messages for ${platform.platformId}: ${err}`);
|
|
67652
67786
|
});
|
|
67653
67787
|
}).catch((err) => {
|
|
67654
|
-
|
|
67788
|
+
log28.warn(`Failed to get bot user for cleanup on ${platform.platformId}: ${err}`);
|
|
67655
67789
|
});
|
|
67656
67790
|
}
|
|
67657
67791
|
if (persisted.size > 0) {
|
|
@@ -67665,10 +67799,10 @@ class SessionManager extends EventEmitter4 {
|
|
|
67665
67799
|
}
|
|
67666
67800
|
}
|
|
67667
67801
|
if (pausedToSkip.length > 0) {
|
|
67668
|
-
|
|
67802
|
+
log28.info(`⏸️ ${pausedToSkip.length} session(s) remain paused (waiting for user message)`);
|
|
67669
67803
|
}
|
|
67670
67804
|
if (activeToResume.length > 0) {
|
|
67671
|
-
|
|
67805
|
+
log28.info(`\uD83D\uDD04 Attempting to resume ${activeToResume.length} active session(s)...`);
|
|
67672
67806
|
for (const state of activeToResume) {
|
|
67673
67807
|
await resumeSession(state, this.getContext());
|
|
67674
67808
|
}
|
|
@@ -67786,6 +67920,24 @@ class SessionManager extends EventEmitter4 {
|
|
|
67786
67920
|
return;
|
|
67787
67921
|
await deferUpdate(session, username, this.autoUpdateManager);
|
|
67788
67922
|
}
|
|
67923
|
+
async pluginList(threadId) {
|
|
67924
|
+
const session = this.findSessionByThreadId(threadId);
|
|
67925
|
+
if (!session)
|
|
67926
|
+
return;
|
|
67927
|
+
await handlePluginList(session);
|
|
67928
|
+
}
|
|
67929
|
+
async pluginInstall(threadId, pluginName, username) {
|
|
67930
|
+
const session = this.findSessionByThreadId(threadId);
|
|
67931
|
+
if (!session)
|
|
67932
|
+
return;
|
|
67933
|
+
await handlePluginInstall(session, pluginName, username, this.getContext());
|
|
67934
|
+
}
|
|
67935
|
+
async pluginUninstall(threadId, pluginName, username) {
|
|
67936
|
+
const session = this.findSessionByThreadId(threadId);
|
|
67937
|
+
if (!session)
|
|
67938
|
+
return;
|
|
67939
|
+
await handlePluginUninstall(session, pluginName, username, this.getContext());
|
|
67940
|
+
}
|
|
67789
67941
|
isSessionInteractive(threadId) {
|
|
67790
67942
|
const session = this.findSessionByThreadId(threadId);
|
|
67791
67943
|
if (!session)
|
|
@@ -67988,7 +68140,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
67988
68140
|
const message = messageBuilder(formatter);
|
|
67989
68141
|
await post(session, "info", message);
|
|
67990
68142
|
} catch (err) {
|
|
67991
|
-
|
|
68143
|
+
log28.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
|
|
67992
68144
|
}
|
|
67993
68145
|
}
|
|
67994
68146
|
}
|
|
@@ -68007,7 +68159,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
68007
68159
|
session.messageManager?.setPendingUpdatePrompt({ postId: post2.id });
|
|
68008
68160
|
this.registerPost(post2.id, session.threadId);
|
|
68009
68161
|
} catch (err) {
|
|
68010
|
-
|
|
68162
|
+
log28.warn(`Failed to post ask message to ${threadId}: ${err}`);
|
|
68011
68163
|
}
|
|
68012
68164
|
}
|
|
68013
68165
|
}
|
|
@@ -76583,29 +76735,29 @@ function SessionLog({ logs, maxLines = 20 }) {
|
|
|
76583
76735
|
return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
76584
76736
|
flexDirection: "column",
|
|
76585
76737
|
flexShrink: 0,
|
|
76586
|
-
children: displayLogs.map((
|
|
76738
|
+
children: displayLogs.map((log29) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
|
|
76587
76739
|
flexShrink: 0,
|
|
76588
76740
|
children: [
|
|
76589
76741
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
76590
|
-
color: getColorForLevel(
|
|
76742
|
+
color: getColorForLevel(log29.level),
|
|
76591
76743
|
dimColor: true,
|
|
76592
76744
|
wrap: "truncate",
|
|
76593
76745
|
children: [
|
|
76594
76746
|
"[",
|
|
76595
|
-
padComponent(
|
|
76747
|
+
padComponent(log29.component),
|
|
76596
76748
|
"]"
|
|
76597
76749
|
]
|
|
76598
76750
|
}, undefined, true, undefined, this),
|
|
76599
76751
|
/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
|
|
76600
|
-
color: getColorForLevel(
|
|
76752
|
+
color: getColorForLevel(log29.level),
|
|
76601
76753
|
wrap: "truncate",
|
|
76602
76754
|
children: [
|
|
76603
76755
|
" ",
|
|
76604
|
-
|
|
76756
|
+
log29.message
|
|
76605
76757
|
]
|
|
76606
76758
|
}, undefined, true, undefined, this)
|
|
76607
76759
|
]
|
|
76608
|
-
},
|
|
76760
|
+
}, log29.id, true, undefined, this))
|
|
76609
76761
|
}, undefined, false, undefined, this);
|
|
76610
76762
|
}
|
|
76611
76763
|
// src/ui/components/Footer.tsx
|
|
@@ -77103,7 +77255,7 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
77103
77255
|
const scrollRef = import_react59.default.useRef(null);
|
|
77104
77256
|
const { stdout } = use_stdout_default();
|
|
77105
77257
|
const isDebug = process.env.DEBUG === "1";
|
|
77106
|
-
const displayLogs = logs.filter((
|
|
77258
|
+
const displayLogs = logs.filter((log29) => isDebug || log29.level !== "debug");
|
|
77107
77259
|
const visibleLogs = displayLogs.slice(-Math.max(maxLines * 3, 100));
|
|
77108
77260
|
import_react59.default.useEffect(() => {
|
|
77109
77261
|
const handleResize = () => scrollRef.current?.remeasure();
|
|
@@ -77143,25 +77295,25 @@ function LogPanel({ logs, maxLines = 10, focused = false }) {
|
|
|
77143
77295
|
overflow: "hidden",
|
|
77144
77296
|
children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(ScrollView, {
|
|
77145
77297
|
ref: scrollRef,
|
|
77146
|
-
children: visibleLogs.map((
|
|
77298
|
+
children: visibleLogs.map((log29) => /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
77147
77299
|
children: [
|
|
77148
77300
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
77149
77301
|
dimColor: true,
|
|
77150
77302
|
children: [
|
|
77151
77303
|
"[",
|
|
77152
|
-
padComponent2(
|
|
77304
|
+
padComponent2(log29.component),
|
|
77153
77305
|
"]"
|
|
77154
77306
|
]
|
|
77155
77307
|
}, undefined, true, undefined, this),
|
|
77156
77308
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
77157
|
-
color: getLevelColor(
|
|
77309
|
+
color: getLevelColor(log29.level),
|
|
77158
77310
|
children: [
|
|
77159
77311
|
" ",
|
|
77160
|
-
|
|
77312
|
+
log29.message
|
|
77161
77313
|
]
|
|
77162
77314
|
}, undefined, true, undefined, this)
|
|
77163
77315
|
]
|
|
77164
|
-
},
|
|
77316
|
+
}, log29.id, true, undefined, this))
|
|
77165
77317
|
}, undefined, false, undefined, this)
|
|
77166
77318
|
}, undefined, false, undefined, this);
|
|
77167
77319
|
}
|
|
@@ -77678,10 +77830,10 @@ function useAppState(initialConfig) {
|
|
|
77678
77830
|
});
|
|
77679
77831
|
}, []);
|
|
77680
77832
|
const getLogsForSession = import_react60.useCallback((sessionId) => {
|
|
77681
|
-
return state.logs.filter((
|
|
77833
|
+
return state.logs.filter((log29) => log29.sessionId === sessionId);
|
|
77682
77834
|
}, [state.logs]);
|
|
77683
77835
|
const getGlobalLogs = import_react60.useCallback(() => {
|
|
77684
|
-
return state.logs.filter((
|
|
77836
|
+
return state.logs.filter((log29) => !log29.sessionId);
|
|
77685
77837
|
}, [state.logs]);
|
|
77686
77838
|
const togglePlatformEnabled = import_react60.useCallback((platformId) => {
|
|
77687
77839
|
let newEnabled = false;
|
|
@@ -78599,14 +78751,6 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
78599
78751
|
}
|
|
78600
78752
|
return;
|
|
78601
78753
|
}
|
|
78602
|
-
case "context":
|
|
78603
|
-
case "cost":
|
|
78604
|
-
case "compact":
|
|
78605
|
-
if (isAllowed) {
|
|
78606
|
-
const claudeCommand = "/" + parsed.command;
|
|
78607
|
-
await session.sendFollowUp(threadRoot, claudeCommand);
|
|
78608
|
-
}
|
|
78609
|
-
return;
|
|
78610
78754
|
case "bug":
|
|
78611
78755
|
if (isAllowed) {
|
|
78612
78756
|
const files3 = post2.metadata?.files;
|
|
@@ -78615,6 +78759,47 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
78615
78759
|
return;
|
|
78616
78760
|
case "kill":
|
|
78617
78761
|
return;
|
|
78762
|
+
case "plugin": {
|
|
78763
|
+
if (!isAllowed)
|
|
78764
|
+
return;
|
|
78765
|
+
const pluginArgs = parsed.args?.split(/\s+/) || [];
|
|
78766
|
+
const subcommand = pluginArgs[0]?.toLowerCase() || "list";
|
|
78767
|
+
const pluginName = pluginArgs.slice(1).join(" ");
|
|
78768
|
+
switch (subcommand) {
|
|
78769
|
+
case "list":
|
|
78770
|
+
await session.pluginList(threadRoot);
|
|
78771
|
+
break;
|
|
78772
|
+
case "install":
|
|
78773
|
+
if (!pluginName) {
|
|
78774
|
+
await client.createPost(`❌ Usage: ${formatter.formatCode("!plugin install <plugin-name>")}`, threadRoot);
|
|
78775
|
+
} else {
|
|
78776
|
+
await session.pluginInstall(threadRoot, pluginName, username);
|
|
78777
|
+
}
|
|
78778
|
+
break;
|
|
78779
|
+
case "uninstall":
|
|
78780
|
+
if (!pluginName) {
|
|
78781
|
+
await client.createPost(`❌ Usage: ${formatter.formatCode("!plugin uninstall <plugin-name>")}`, threadRoot);
|
|
78782
|
+
} else {
|
|
78783
|
+
await session.pluginUninstall(threadRoot, pluginName, username);
|
|
78784
|
+
}
|
|
78785
|
+
break;
|
|
78786
|
+
default:
|
|
78787
|
+
await client.createPost(`❌ Unknown subcommand: ${formatter.formatCode(subcommand)}
|
|
78788
|
+
Usage: ${formatter.formatCode("!plugin <list|install|uninstall> [name]")}`, threadRoot);
|
|
78789
|
+
}
|
|
78790
|
+
return;
|
|
78791
|
+
}
|
|
78792
|
+
default: {
|
|
78793
|
+
const defaultPassthroughCommands = new Set(["context", "cost", "compact"]);
|
|
78794
|
+
const availableCommands = activeSession.availableSlashCommands ?? defaultPassthroughCommands;
|
|
78795
|
+
if (availableCommands.has(parsed.command)) {
|
|
78796
|
+
if (isAllowed) {
|
|
78797
|
+
const claudeCommand = "/" + parsed.command + (parsed.args ? " " + parsed.args : "");
|
|
78798
|
+
await session.sendFollowUp(threadRoot, claudeCommand);
|
|
78799
|
+
}
|
|
78800
|
+
}
|
|
78801
|
+
return;
|
|
78802
|
+
}
|
|
78618
78803
|
}
|
|
78619
78804
|
}
|
|
78620
78805
|
if (session.hasPendingWorktreePrompt(threadRoot)) {
|
|
@@ -78691,7 +78876,7 @@ import { EventEmitter as EventEmitter9 } from "events";
|
|
|
78691
78876
|
|
|
78692
78877
|
// src/auto-update/checker.ts
|
|
78693
78878
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
78694
|
-
var
|
|
78879
|
+
var log29 = createLogger("checker");
|
|
78695
78880
|
var PACKAGE_NAME = "claude-threads";
|
|
78696
78881
|
function compareVersions(a, b) {
|
|
78697
78882
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -78714,13 +78899,13 @@ async function fetchLatestVersion() {
|
|
|
78714
78899
|
}
|
|
78715
78900
|
});
|
|
78716
78901
|
if (!response.ok) {
|
|
78717
|
-
|
|
78902
|
+
log29.warn(`Failed to fetch latest version: HTTP ${response.status}`);
|
|
78718
78903
|
return null;
|
|
78719
78904
|
}
|
|
78720
78905
|
const data = await response.json();
|
|
78721
78906
|
return data.version ?? null;
|
|
78722
78907
|
} catch (err) {
|
|
78723
|
-
|
|
78908
|
+
log29.warn(`Failed to fetch latest version: ${err}`);
|
|
78724
78909
|
return null;
|
|
78725
78910
|
}
|
|
78726
78911
|
}
|
|
@@ -78737,38 +78922,38 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
78737
78922
|
}
|
|
78738
78923
|
start() {
|
|
78739
78924
|
if (!this.config.enabled) {
|
|
78740
|
-
|
|
78925
|
+
log29.debug("Auto-update disabled, not starting checker");
|
|
78741
78926
|
return;
|
|
78742
78927
|
}
|
|
78743
78928
|
setTimeout(() => {
|
|
78744
78929
|
this.check().catch((err) => {
|
|
78745
|
-
|
|
78930
|
+
log29.warn(`Initial update check failed: ${err}`);
|
|
78746
78931
|
});
|
|
78747
78932
|
}, 5000);
|
|
78748
78933
|
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
78749
78934
|
this.checkInterval = setInterval(() => {
|
|
78750
78935
|
this.check().catch((err) => {
|
|
78751
|
-
|
|
78936
|
+
log29.warn(`Periodic update check failed: ${err}`);
|
|
78752
78937
|
});
|
|
78753
78938
|
}, intervalMs);
|
|
78754
|
-
|
|
78939
|
+
log29.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
|
|
78755
78940
|
}
|
|
78756
78941
|
stop() {
|
|
78757
78942
|
if (this.checkInterval) {
|
|
78758
78943
|
clearInterval(this.checkInterval);
|
|
78759
78944
|
this.checkInterval = null;
|
|
78760
78945
|
}
|
|
78761
|
-
|
|
78946
|
+
log29.debug("Update checker stopped");
|
|
78762
78947
|
}
|
|
78763
78948
|
async check() {
|
|
78764
78949
|
if (this.isChecking) {
|
|
78765
|
-
|
|
78950
|
+
log29.debug("Check already in progress, skipping");
|
|
78766
78951
|
return this.lastUpdateInfo;
|
|
78767
78952
|
}
|
|
78768
78953
|
this.isChecking = true;
|
|
78769
78954
|
this.emit("check:start");
|
|
78770
78955
|
try {
|
|
78771
|
-
|
|
78956
|
+
log29.debug("Checking for updates...");
|
|
78772
78957
|
const latestVersion2 = await fetchLatestVersion();
|
|
78773
78958
|
if (!latestVersion2) {
|
|
78774
78959
|
this.emit("check:complete", false);
|
|
@@ -78785,18 +78970,18 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
78785
78970
|
detectedAt: new Date
|
|
78786
78971
|
};
|
|
78787
78972
|
if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
|
|
78788
|
-
|
|
78973
|
+
log29.info(`\uD83C\uDD95 Update available: v${currentVersion} → v${latestVersion2}`);
|
|
78789
78974
|
this.lastUpdateInfo = updateInfo;
|
|
78790
78975
|
this.emit("update", updateInfo);
|
|
78791
78976
|
}
|
|
78792
78977
|
this.emit("check:complete", true);
|
|
78793
78978
|
return updateInfo;
|
|
78794
78979
|
}
|
|
78795
|
-
|
|
78980
|
+
log29.debug(`Up to date (v${currentVersion})`);
|
|
78796
78981
|
this.emit("check:complete", false);
|
|
78797
78982
|
return null;
|
|
78798
78983
|
} catch (err) {
|
|
78799
|
-
|
|
78984
|
+
log29.warn(`Update check failed: ${err}`);
|
|
78800
78985
|
this.emit("check:error", err);
|
|
78801
78986
|
return null;
|
|
78802
78987
|
} finally {
|
|
@@ -78866,7 +79051,7 @@ function isInScheduledWindow(window2) {
|
|
|
78866
79051
|
}
|
|
78867
79052
|
|
|
78868
79053
|
// src/auto-update/scheduler.ts
|
|
78869
|
-
var
|
|
79054
|
+
var log30 = createLogger("scheduler");
|
|
78870
79055
|
|
|
78871
79056
|
class UpdateScheduler extends EventEmitter8 {
|
|
78872
79057
|
config;
|
|
@@ -78890,7 +79075,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78890
79075
|
scheduleUpdate(updateInfo) {
|
|
78891
79076
|
this.pendingUpdate = updateInfo;
|
|
78892
79077
|
if (this.config.autoRestartMode === "immediate") {
|
|
78893
|
-
|
|
79078
|
+
log30.info("Immediate mode: triggering update now");
|
|
78894
79079
|
this.emit("ready", updateInfo);
|
|
78895
79080
|
return;
|
|
78896
79081
|
}
|
|
@@ -78903,19 +79088,19 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78903
79088
|
this.scheduledRestartAt = null;
|
|
78904
79089
|
this.askApprovals.clear();
|
|
78905
79090
|
this.askStartTime = null;
|
|
78906
|
-
|
|
79091
|
+
log30.debug("Update schedule cancelled");
|
|
78907
79092
|
}
|
|
78908
79093
|
deferUpdate(minutes) {
|
|
78909
79094
|
const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
|
|
78910
79095
|
this.scheduledRestartAt = null;
|
|
78911
79096
|
this.idleStartTime = null;
|
|
78912
79097
|
this.emit("deferred", deferUntil);
|
|
78913
|
-
|
|
79098
|
+
log30.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
|
|
78914
79099
|
return deferUntil;
|
|
78915
79100
|
}
|
|
78916
79101
|
recordAskResponse(threadId, approved) {
|
|
78917
79102
|
this.askApprovals.set(threadId, approved);
|
|
78918
|
-
|
|
79103
|
+
log30.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
|
|
78919
79104
|
this.checkAskCondition();
|
|
78920
79105
|
}
|
|
78921
79106
|
getScheduledRestartAt() {
|
|
@@ -78936,7 +79121,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78936
79121
|
return;
|
|
78937
79122
|
this.checkCondition();
|
|
78938
79123
|
this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
|
|
78939
|
-
|
|
79124
|
+
log30.debug(`Started checking for ${this.config.autoRestartMode} condition`);
|
|
78940
79125
|
}
|
|
78941
79126
|
stopChecking() {
|
|
78942
79127
|
if (this.checkTimer) {
|
|
@@ -78967,17 +79152,17 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78967
79152
|
if (activity.activeSessionCount === 0) {
|
|
78968
79153
|
if (!this.idleStartTime) {
|
|
78969
79154
|
this.idleStartTime = new Date;
|
|
78970
|
-
|
|
79155
|
+
log30.debug("No active sessions, starting idle timer");
|
|
78971
79156
|
}
|
|
78972
79157
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
78973
79158
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
78974
79159
|
if (idleMs >= requiredMs) {
|
|
78975
|
-
|
|
79160
|
+
log30.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
|
|
78976
79161
|
this.triggerCountdown();
|
|
78977
79162
|
}
|
|
78978
79163
|
} else {
|
|
78979
79164
|
if (this.idleStartTime) {
|
|
78980
|
-
|
|
79165
|
+
log30.debug("Sessions became active, resetting idle timer");
|
|
78981
79166
|
this.idleStartTime = null;
|
|
78982
79167
|
}
|
|
78983
79168
|
}
|
|
@@ -78988,7 +79173,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78988
79173
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
78989
79174
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
78990
79175
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
78991
|
-
|
|
79176
|
+
log30.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
|
|
78992
79177
|
this.triggerCountdown();
|
|
78993
79178
|
}
|
|
78994
79179
|
} else if (activity.activeSessionCount === 0) {
|
|
@@ -78998,7 +79183,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
78998
79183
|
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
78999
79184
|
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
79000
79185
|
if (idleMs >= requiredMs) {
|
|
79001
|
-
|
|
79186
|
+
log30.info("No sessions and quiet timeout reached, triggering update");
|
|
79002
79187
|
this.triggerCountdown();
|
|
79003
79188
|
}
|
|
79004
79189
|
}
|
|
@@ -79009,13 +79194,13 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79009
79194
|
}
|
|
79010
79195
|
const activity = this.getSessionActivity();
|
|
79011
79196
|
if (activity.activeSessionCount === 0) {
|
|
79012
|
-
|
|
79197
|
+
log30.info("Within scheduled window and no active sessions, triggering update");
|
|
79013
79198
|
this.triggerCountdown();
|
|
79014
79199
|
} else if (activity.lastActivityAt) {
|
|
79015
79200
|
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
79016
79201
|
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
79017
79202
|
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
79018
|
-
|
|
79203
|
+
log30.info("Within scheduled window and sessions quiet, triggering update");
|
|
79019
79204
|
this.triggerCountdown();
|
|
79020
79205
|
}
|
|
79021
79206
|
}
|
|
@@ -79023,14 +79208,14 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79023
79208
|
checkAskCondition() {
|
|
79024
79209
|
const threadIds = this.getActiveThreadIds();
|
|
79025
79210
|
if (threadIds.length === 0) {
|
|
79026
|
-
|
|
79211
|
+
log30.info("No active threads, proceeding with update");
|
|
79027
79212
|
this.triggerCountdown();
|
|
79028
79213
|
return;
|
|
79029
79214
|
}
|
|
79030
79215
|
if (!this.askStartTime && this.pendingUpdate) {
|
|
79031
79216
|
this.askStartTime = new Date;
|
|
79032
79217
|
this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
|
|
79033
|
-
|
|
79218
|
+
log30.warn(`Failed to post ask message: ${err}`);
|
|
79034
79219
|
});
|
|
79035
79220
|
return;
|
|
79036
79221
|
}
|
|
@@ -79043,12 +79228,12 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79043
79228
|
denials++;
|
|
79044
79229
|
}
|
|
79045
79230
|
if (approvals > threadIds.length / 2) {
|
|
79046
|
-
|
|
79231
|
+
log30.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
|
|
79047
79232
|
this.triggerCountdown();
|
|
79048
79233
|
return;
|
|
79049
79234
|
}
|
|
79050
79235
|
if (denials > threadIds.length / 2) {
|
|
79051
|
-
|
|
79236
|
+
log30.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
|
|
79052
79237
|
this.deferUpdate(60);
|
|
79053
79238
|
return;
|
|
79054
79239
|
}
|
|
@@ -79056,7 +79241,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79056
79241
|
const elapsedMs = Date.now() - this.askStartTime.getTime();
|
|
79057
79242
|
const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
|
|
79058
79243
|
if (elapsedMs >= timeoutMs) {
|
|
79059
|
-
|
|
79244
|
+
log30.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
|
|
79060
79245
|
this.triggerCountdown();
|
|
79061
79246
|
}
|
|
79062
79247
|
}
|
|
@@ -79076,7 +79261,7 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79076
79261
|
this.emit("ready", this.pendingUpdate);
|
|
79077
79262
|
}
|
|
79078
79263
|
}, 1000);
|
|
79079
|
-
|
|
79264
|
+
log30.info("Update countdown started (60 seconds)");
|
|
79080
79265
|
}
|
|
79081
79266
|
stopCountdown() {
|
|
79082
79267
|
if (this.countdownTimer) {
|
|
@@ -79087,11 +79272,69 @@ class UpdateScheduler extends EventEmitter8 {
|
|
|
79087
79272
|
}
|
|
79088
79273
|
|
|
79089
79274
|
// src/auto-update/installer.ts
|
|
79090
|
-
import { spawn as
|
|
79275
|
+
import { spawn as spawn8, spawnSync } from "child_process";
|
|
79091
79276
|
import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
79092
79277
|
import { dirname as dirname8, resolve as resolve6 } from "path";
|
|
79093
79278
|
import { homedir as homedir5 } from "os";
|
|
79094
|
-
var
|
|
79279
|
+
var log31 = createLogger("installer");
|
|
79280
|
+
function detectPackageManager() {
|
|
79281
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
79282
|
+
const originalInstaller = detectOriginalInstaller();
|
|
79283
|
+
if (originalInstaller) {
|
|
79284
|
+
log31.debug(`Detected original installer: ${originalInstaller}`);
|
|
79285
|
+
if (originalInstaller === "bun") {
|
|
79286
|
+
const bunCheck2 = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
79287
|
+
if (bunCheck2.status === 0) {
|
|
79288
|
+
return { cmd: "bun", isBun: true };
|
|
79289
|
+
}
|
|
79290
|
+
log31.warn("Originally installed with bun, but bun not found. Falling back to npm.");
|
|
79291
|
+
} else {
|
|
79292
|
+
const npmCheck2 = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
|
|
79293
|
+
if (npmCheck2.status === 0) {
|
|
79294
|
+
return { cmd: npmCmd, isBun: false };
|
|
79295
|
+
}
|
|
79296
|
+
log31.warn("Originally installed with npm, but npm not found. Falling back to bun.");
|
|
79297
|
+
}
|
|
79298
|
+
}
|
|
79299
|
+
const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
79300
|
+
if (bunCheck.status === 0) {
|
|
79301
|
+
return { cmd: "bun", isBun: true };
|
|
79302
|
+
}
|
|
79303
|
+
const npmCheck = spawnSync(npmCmd, ["--version"], { stdio: "ignore" });
|
|
79304
|
+
if (npmCheck.status === 0) {
|
|
79305
|
+
return { cmd: npmCmd, isBun: false };
|
|
79306
|
+
}
|
|
79307
|
+
return null;
|
|
79308
|
+
}
|
|
79309
|
+
function normalizePath(p) {
|
|
79310
|
+
if (process.platform === "win32") {
|
|
79311
|
+
return p.toLowerCase();
|
|
79312
|
+
}
|
|
79313
|
+
return p;
|
|
79314
|
+
}
|
|
79315
|
+
function detectOriginalInstaller() {
|
|
79316
|
+
try {
|
|
79317
|
+
const scriptPath = normalizePath(process.argv[1] || "");
|
|
79318
|
+
const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve6(homedir5(), ".bun"));
|
|
79319
|
+
if (scriptPath.startsWith(bunGlobalDir)) {
|
|
79320
|
+
return "bun";
|
|
79321
|
+
}
|
|
79322
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
79323
|
+
const npmPrefixResult = spawnSync(npmCmd, ["prefix", "-g"], {
|
|
79324
|
+
encoding: "utf-8",
|
|
79325
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
79326
|
+
});
|
|
79327
|
+
if (npmPrefixResult.status === 0 && npmPrefixResult.stdout) {
|
|
79328
|
+
const npmPrefix2 = normalizePath(npmPrefixResult.stdout.trim());
|
|
79329
|
+
if (scriptPath.startsWith(npmPrefix2)) {
|
|
79330
|
+
return "npm";
|
|
79331
|
+
}
|
|
79332
|
+
}
|
|
79333
|
+
return null;
|
|
79334
|
+
} catch {
|
|
79335
|
+
return null;
|
|
79336
|
+
}
|
|
79337
|
+
}
|
|
79095
79338
|
var STATE_PATH = resolve6(homedir5(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
|
|
79096
79339
|
var PACKAGE_NAME2 = "claude-threads";
|
|
79097
79340
|
function loadUpdateState() {
|
|
@@ -79101,7 +79344,7 @@ function loadUpdateState() {
|
|
|
79101
79344
|
return JSON.parse(content);
|
|
79102
79345
|
}
|
|
79103
79346
|
} catch (err) {
|
|
79104
|
-
|
|
79347
|
+
log31.warn(`Failed to load update state: ${err}`);
|
|
79105
79348
|
}
|
|
79106
79349
|
return {};
|
|
79107
79350
|
}
|
|
@@ -79112,9 +79355,9 @@ function saveUpdateState(state) {
|
|
|
79112
79355
|
mkdirSync4(dir, { recursive: true });
|
|
79113
79356
|
}
|
|
79114
79357
|
writeFileSync5(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
79115
|
-
|
|
79358
|
+
log31.debug("Update state saved");
|
|
79116
79359
|
} catch (err) {
|
|
79117
|
-
|
|
79360
|
+
log31.warn(`Failed to save update state: ${err}`);
|
|
79118
79361
|
}
|
|
79119
79362
|
}
|
|
79120
79363
|
function clearUpdateState() {
|
|
@@ -79123,7 +79366,7 @@ function clearUpdateState() {
|
|
|
79123
79366
|
writeFileSync5(STATE_PATH, "{}", "utf-8");
|
|
79124
79367
|
}
|
|
79125
79368
|
} catch (err) {
|
|
79126
|
-
|
|
79369
|
+
log31.warn(`Failed to clear update state: ${err}`);
|
|
79127
79370
|
}
|
|
79128
79371
|
}
|
|
79129
79372
|
function checkJustUpdated() {
|
|
@@ -79155,7 +79398,13 @@ function clearRuntimeSettings() {
|
|
|
79155
79398
|
}
|
|
79156
79399
|
}
|
|
79157
79400
|
async function installVersion(version) {
|
|
79158
|
-
|
|
79401
|
+
log31.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME2}@${version}...`);
|
|
79402
|
+
const pm = detectPackageManager();
|
|
79403
|
+
if (!pm) {
|
|
79404
|
+
const error = "Neither bun nor npm found in PATH. Cannot install update.";
|
|
79405
|
+
log31.error(`❌ ${error}`);
|
|
79406
|
+
return { success: false, error };
|
|
79407
|
+
}
|
|
79159
79408
|
saveUpdateState({
|
|
79160
79409
|
previousVersion: VERSION,
|
|
79161
79410
|
targetVersion: version,
|
|
@@ -79163,11 +79412,10 @@ async function installVersion(version) {
|
|
|
79163
79412
|
justUpdated: false
|
|
79164
79413
|
});
|
|
79165
79414
|
return new Promise((resolve7) => {
|
|
79166
|
-
const
|
|
79167
|
-
const
|
|
79168
|
-
|
|
79169
|
-
|
|
79170
|
-
const child = spawn7(cmd, args, {
|
|
79415
|
+
const { cmd, isBun: isBun3 } = pm;
|
|
79416
|
+
const args = ["install", "-g", `${PACKAGE_NAME2}@${version}`];
|
|
79417
|
+
log31.debug(`Using ${isBun3 ? "bun" : "npm"} for installation`);
|
|
79418
|
+
const child = spawn8(cmd, args, {
|
|
79171
79419
|
stdio: ["ignore", "pipe", "pipe"],
|
|
79172
79420
|
env: {
|
|
79173
79421
|
...process.env,
|
|
@@ -79184,7 +79432,7 @@ async function installVersion(version) {
|
|
|
79184
79432
|
});
|
|
79185
79433
|
child.on("close", (code) => {
|
|
79186
79434
|
if (code === 0) {
|
|
79187
|
-
|
|
79435
|
+
log31.info(`✅ Successfully installed ${PACKAGE_NAME2}@${version}`);
|
|
79188
79436
|
saveUpdateState({
|
|
79189
79437
|
previousVersion: VERSION,
|
|
79190
79438
|
targetVersion: version,
|
|
@@ -79194,20 +79442,20 @@ async function installVersion(version) {
|
|
|
79194
79442
|
resolve7({ success: true });
|
|
79195
79443
|
} else {
|
|
79196
79444
|
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
79197
|
-
|
|
79445
|
+
log31.error(`❌ Installation failed: ${errorMsg}`);
|
|
79198
79446
|
clearUpdateState();
|
|
79199
79447
|
resolve7({ success: false, error: errorMsg });
|
|
79200
79448
|
}
|
|
79201
79449
|
});
|
|
79202
79450
|
child.on("error", (err) => {
|
|
79203
|
-
|
|
79451
|
+
log31.error(`❌ Failed to spawn npm: ${err}`);
|
|
79204
79452
|
clearUpdateState();
|
|
79205
79453
|
resolve7({ success: false, error: err.message });
|
|
79206
79454
|
});
|
|
79207
79455
|
setTimeout(() => {
|
|
79208
79456
|
if (child.exitCode === null) {
|
|
79209
79457
|
child.kill();
|
|
79210
|
-
|
|
79458
|
+
log31.error("❌ Installation timed out");
|
|
79211
79459
|
clearUpdateState();
|
|
79212
79460
|
resolve7({ success: false, error: "Installation timed out" });
|
|
79213
79461
|
}
|
|
@@ -79215,7 +79463,8 @@ async function installVersion(version) {
|
|
|
79215
79463
|
});
|
|
79216
79464
|
}
|
|
79217
79465
|
function getRollbackInstructions(previousVersion) {
|
|
79218
|
-
const
|
|
79466
|
+
const pm = detectPackageManager();
|
|
79467
|
+
const cmd = pm?.isBun ? "bun" : "npm";
|
|
79219
79468
|
return `To rollback to the previous version, run:
|
|
79220
79469
|
${cmd} install -g ${PACKAGE_NAME2}@${previousVersion}`;
|
|
79221
79470
|
}
|
|
@@ -79248,7 +79497,7 @@ class UpdateInstaller {
|
|
|
79248
79497
|
}
|
|
79249
79498
|
|
|
79250
79499
|
// src/auto-update/manager.ts
|
|
79251
|
-
var
|
|
79500
|
+
var log32 = createLogger("updater");
|
|
79252
79501
|
|
|
79253
79502
|
class AutoUpdateManager extends EventEmitter9 {
|
|
79254
79503
|
config;
|
|
@@ -79271,23 +79520,23 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
79271
79520
|
}
|
|
79272
79521
|
start() {
|
|
79273
79522
|
if (!this.config.enabled) {
|
|
79274
|
-
|
|
79523
|
+
log32.info("Auto-update is disabled");
|
|
79275
79524
|
return;
|
|
79276
79525
|
}
|
|
79277
79526
|
const updateResult = this.installer.checkJustUpdated();
|
|
79278
79527
|
if (updateResult) {
|
|
79279
|
-
|
|
79528
|
+
log32.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
|
|
79280
79529
|
this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
|
|
79281
|
-
|
|
79530
|
+
log32.warn(`Failed to broadcast update notification: ${err}`);
|
|
79282
79531
|
});
|
|
79283
79532
|
}
|
|
79284
79533
|
this.checker.start();
|
|
79285
|
-
|
|
79534
|
+
log32.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
|
|
79286
79535
|
}
|
|
79287
79536
|
stop() {
|
|
79288
79537
|
this.checker.stop();
|
|
79289
79538
|
this.scheduler.stop();
|
|
79290
|
-
|
|
79539
|
+
log32.debug("Auto-update manager stopped");
|
|
79291
79540
|
}
|
|
79292
79541
|
getState() {
|
|
79293
79542
|
return { ...this.state };
|
|
@@ -79301,10 +79550,10 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
79301
79550
|
async forceUpdate() {
|
|
79302
79551
|
const updateInfo = this.state.updateInfo || await this.checker.check();
|
|
79303
79552
|
if (!updateInfo) {
|
|
79304
|
-
|
|
79553
|
+
log32.info("No update available");
|
|
79305
79554
|
return;
|
|
79306
79555
|
}
|
|
79307
|
-
|
|
79556
|
+
log32.info("Forcing immediate update");
|
|
79308
79557
|
await this.performUpdate(updateInfo);
|
|
79309
79558
|
}
|
|
79310
79559
|
deferUpdate(minutes = 60) {
|
|
@@ -79359,7 +79608,7 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
79359
79608
|
await this.callbacks.broadcastUpdate((fmt) => `✅ ${fmt.formatBold("Update installed")} - restarting now. ${fmt.formatItalic("Sessions will resume automatically.")}`).catch(() => {});
|
|
79360
79609
|
await new Promise((resolve7) => setTimeout(resolve7, 1000));
|
|
79361
79610
|
await this.callbacks.prepareForRestart();
|
|
79362
|
-
|
|
79611
|
+
log32.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
|
|
79363
79612
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
79364
79613
|
process.stdout.write("\x1B[?25h");
|
|
79365
79614
|
process.exit(RESTART_EXIT_CODE);
|
|
@@ -79447,7 +79696,7 @@ async function main() {
|
|
|
79447
79696
|
return false;
|
|
79448
79697
|
};
|
|
79449
79698
|
if (await shouldUseAutoRestart()) {
|
|
79450
|
-
const { spawn:
|
|
79699
|
+
const { spawn: spawn9 } = await import("child_process");
|
|
79451
79700
|
const { dirname: dirname9, resolve: resolve7 } = await import("path");
|
|
79452
79701
|
const { fileURLToPath: fileURLToPath7 } = await import("url");
|
|
79453
79702
|
const __filename2 = fileURLToPath7(import.meta.url);
|
|
@@ -79457,7 +79706,7 @@ async function main() {
|
|
|
79457
79706
|
console.log("\uD83D\uDD04 Starting with auto-restart enabled...");
|
|
79458
79707
|
console.log("");
|
|
79459
79708
|
const binPath = __filename2;
|
|
79460
|
-
const child =
|
|
79709
|
+
const child = spawn9(daemonPath, ["--restart-on-error", ...args], {
|
|
79461
79710
|
stdio: "inherit",
|
|
79462
79711
|
env: {
|
|
79463
79712
|
...process.env,
|
|
@@ -42509,6 +42509,19 @@ var COMMAND_REGISTRY = [
|
|
|
42509
42509
|
claudeCanExecute: true,
|
|
42510
42510
|
returnsResultToClaude: false
|
|
42511
42511
|
},
|
|
42512
|
+
{
|
|
42513
|
+
command: "plugin",
|
|
42514
|
+
description: "Manage Claude Code plugins",
|
|
42515
|
+
args: "<subcommand> [name]",
|
|
42516
|
+
category: "system",
|
|
42517
|
+
audience: "user",
|
|
42518
|
+
claudeNotes: "User manages plugins themselves",
|
|
42519
|
+
subcommands: [
|
|
42520
|
+
{ name: "list", description: "List installed plugins" },
|
|
42521
|
+
{ name: "install", description: "Install a plugin (restarts Claude)", args: "<name>" },
|
|
42522
|
+
{ name: "uninstall", description: "Uninstall a plugin (restarts Claude)", args: "<name>" }
|
|
42523
|
+
]
|
|
42524
|
+
},
|
|
42512
42525
|
{
|
|
42513
42526
|
command: "context",
|
|
42514
42527
|
description: "Show context usage",
|