claude-threads 1.36.1 → 1.37.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 +13 -0
- package/bin/claude-threads-daemon +8 -0
- package/dist/index.js +302 -73
- package/dist/mcp/mcp-server.js +93 -86
- package/docs/CONFIGURATION.md +27 -0
- package/docs/systemd/claude-threads.service +3 -0
- package/docs/turn-marker-spec.md +149 -0
- package/package.json +1 -1
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -12676,6 +12676,29 @@ var init_emoji = __esm(() => {
|
|
|
12676
12676
|
ALLOW_ALL_EMOJIS = ["white_check_mark", "heavy_check_mark"];
|
|
12677
12677
|
});
|
|
12678
12678
|
|
|
12679
|
+
// src/config/types.ts
|
|
12680
|
+
var STDIO_KEYS, REMOTE_KEYS;
|
|
12681
|
+
var init_types = __esm(() => {
|
|
12682
|
+
STDIO_KEYS = new Set(["type", "command", "args", "env"]);
|
|
12683
|
+
REMOTE_KEYS = new Set(["type", "url", "headers"]);
|
|
12684
|
+
});
|
|
12685
|
+
|
|
12686
|
+
// src/utils/spawn.ts
|
|
12687
|
+
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
|
|
12688
|
+
function addWindowsShell(options) {
|
|
12689
|
+
if (isWindows && options.shell === undefined) {
|
|
12690
|
+
return { ...options, shell: true };
|
|
12691
|
+
}
|
|
12692
|
+
return options;
|
|
12693
|
+
}
|
|
12694
|
+
function crossSpawn(command, args, options) {
|
|
12695
|
+
return nodeSpawn(command, args, addWindowsShell(options ?? {}));
|
|
12696
|
+
}
|
|
12697
|
+
var isWindows;
|
|
12698
|
+
var init_spawn = __esm(() => {
|
|
12699
|
+
isWindows = process.platform === "win32";
|
|
12700
|
+
});
|
|
12701
|
+
|
|
12679
12702
|
// src/utils/logger.ts
|
|
12680
12703
|
function createLogger(component, useStderr = false, sessionId) {
|
|
12681
12704
|
const isDebug = () => process.env.DEBUG === "1";
|
|
@@ -14593,7 +14616,7 @@ var require_semver2 = __commonJS(function(exports, module) {
|
|
|
14593
14616
|
|
|
14594
14617
|
// src/claude/version-check.ts
|
|
14595
14618
|
import { execSync } from "child_process";
|
|
14596
|
-
import { existsSync
|
|
14619
|
+
import { existsSync } from "fs";
|
|
14597
14620
|
import { join } from "path";
|
|
14598
14621
|
function tryClaudeVersion(claudePath) {
|
|
14599
14622
|
try {
|
|
@@ -14646,7 +14669,7 @@ function getClaudePath() {
|
|
|
14646
14669
|
return whichResult;
|
|
14647
14670
|
}
|
|
14648
14671
|
for (const path of COMMON_CLAUDE_PATHS) {
|
|
14649
|
-
if (
|
|
14672
|
+
if (existsSync(path)) {
|
|
14650
14673
|
const result = tryClaudeVersion(path);
|
|
14651
14674
|
if (!result.error) {
|
|
14652
14675
|
discoveredClaudePath = path;
|
|
@@ -14674,29 +14697,6 @@ var init_version_check = __esm(() => {
|
|
|
14674
14697
|
];
|
|
14675
14698
|
});
|
|
14676
14699
|
|
|
14677
|
-
// src/utils/spawn.ts
|
|
14678
|
-
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
|
|
14679
|
-
function addWindowsShell(options) {
|
|
14680
|
-
if (isWindows && options.shell === undefined) {
|
|
14681
|
-
return { ...options, shell: true };
|
|
14682
|
-
}
|
|
14683
|
-
return options;
|
|
14684
|
-
}
|
|
14685
|
-
function crossSpawn(command, args, options) {
|
|
14686
|
-
return nodeSpawn(command, args, addWindowsShell(options ?? {}));
|
|
14687
|
-
}
|
|
14688
|
-
var isWindows;
|
|
14689
|
-
var init_spawn = __esm(() => {
|
|
14690
|
-
isWindows = process.platform === "win32";
|
|
14691
|
-
});
|
|
14692
|
-
|
|
14693
|
-
// src/config/types.ts
|
|
14694
|
-
var STDIO_KEYS, REMOTE_KEYS;
|
|
14695
|
-
var init_types = __esm(() => {
|
|
14696
|
-
STDIO_KEYS = new Set(["type", "command", "args", "env"]);
|
|
14697
|
-
REMOTE_KEYS = new Set(["type", "url", "headers"]);
|
|
14698
|
-
});
|
|
14699
|
-
|
|
14700
14700
|
// src/mcp/outbound-env.ts
|
|
14701
14701
|
var OUTBOUND_ENV;
|
|
14702
14702
|
var init_outbound_env = __esm(() => {
|
|
@@ -14759,7 +14759,7 @@ function buildClaudeChildEnv(parentEnv, account, opts) {
|
|
|
14759
14759
|
}
|
|
14760
14760
|
return env;
|
|
14761
14761
|
}
|
|
14762
|
-
var
|
|
14762
|
+
var log, STDERR_AGGREGATE_SOFT_CAP;
|
|
14763
14763
|
var init_cli = __esm(() => {
|
|
14764
14764
|
init_types();
|
|
14765
14765
|
init_spawn();
|
|
@@ -14768,7 +14768,7 @@ var init_cli = __esm(() => {
|
|
|
14768
14768
|
init_outbound_env();
|
|
14769
14769
|
init_agent_features_env();
|
|
14770
14770
|
init_rate_limit_detector();
|
|
14771
|
-
|
|
14771
|
+
log = createLogger("claude");
|
|
14772
14772
|
STDERR_AGGREGATE_SOFT_CAP = 10 * 1024 * 1024;
|
|
14773
14773
|
});
|
|
14774
14774
|
|
|
@@ -15007,7 +15007,7 @@ var init_render = __esm(() => {
|
|
|
15007
15007
|
});
|
|
15008
15008
|
|
|
15009
15009
|
// src/usage/index.ts
|
|
15010
|
-
import { homedir as
|
|
15010
|
+
import { homedir as homedir2 } from "os";
|
|
15011
15011
|
import path4 from "path";
|
|
15012
15012
|
function toLimits(usage) {
|
|
15013
15013
|
const limits = [
|
|
@@ -15057,7 +15057,7 @@ async function collectUsage(options) {
|
|
|
15057
15057
|
}
|
|
15058
15058
|
return results;
|
|
15059
15059
|
}
|
|
15060
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path4.join(
|
|
15060
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path4.join(homedir2(), ".claude");
|
|
15061
15061
|
return [await readSeat(profileNameFor(configDir), configDir)];
|
|
15062
15062
|
}
|
|
15063
15063
|
var USAGE_PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -33912,9 +33912,11 @@ function formatToolForPermission(toolName, input, formatter, options = {}) {
|
|
|
33912
33912
|
});
|
|
33913
33913
|
return result.permissionText ?? result.display ?? toolName;
|
|
33914
33914
|
}
|
|
33915
|
+
// src/operations/transformer.ts
|
|
33916
|
+
init_cli();
|
|
33915
33917
|
// src/session/lifecycle-fsm.ts
|
|
33916
33918
|
init_logger();
|
|
33917
|
-
var
|
|
33919
|
+
var log2 = createLogger("fsm");
|
|
33918
33920
|
var ALLOWED_TRANSITIONS = {
|
|
33919
33921
|
starting: new Set(["active", "paused", "interrupted", "cancelling", "restarting"]),
|
|
33920
33922
|
active: new Set([
|
|
@@ -33956,7 +33958,7 @@ function createSessionLog(baseLog) {
|
|
|
33956
33958
|
init_logger();
|
|
33957
33959
|
|
|
33958
33960
|
// src/version.ts
|
|
33959
|
-
import { readFileSync, existsSync } from "fs";
|
|
33961
|
+
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
33960
33962
|
import { dirname, resolve } from "path";
|
|
33961
33963
|
import { fileURLToPath } from "url";
|
|
33962
33964
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
@@ -33967,7 +33969,7 @@ function loadPackageJson() {
|
|
|
33967
33969
|
resolve(process.cwd(), "package.json")
|
|
33968
33970
|
];
|
|
33969
33971
|
for (const candidate of candidates) {
|
|
33970
|
-
if (
|
|
33972
|
+
if (existsSync2(candidate)) {
|
|
33971
33973
|
try {
|
|
33972
33974
|
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
33973
33975
|
if (pkg.name === "claude-threads") {
|
|
@@ -33995,23 +33997,34 @@ function formatShortId(id) {
|
|
|
33995
33997
|
}
|
|
33996
33998
|
|
|
33997
33999
|
// src/utils/error-handler/index.ts
|
|
33998
|
-
var
|
|
34000
|
+
var log3 = createLogger("error");
|
|
33999
34001
|
|
|
34000
34002
|
// src/operations/post-helpers/index.ts
|
|
34001
34003
|
init_emoji();
|
|
34002
34004
|
|
|
34003
34005
|
// src/git/worktree.ts
|
|
34004
34006
|
init_spawn();
|
|
34007
|
+
|
|
34008
|
+
// src/utils/state-home.ts
|
|
34009
|
+
import { homedir } from "os";
|
|
34010
|
+
import { resolve as resolve2 } from "path";
|
|
34011
|
+
function stateHome() {
|
|
34012
|
+
return resolve2(process.env.CLAUDE_THREADS_HOME || homedir());
|
|
34013
|
+
}
|
|
34014
|
+
|
|
34015
|
+
// src/git/worktree.ts
|
|
34005
34016
|
import * as path from "path";
|
|
34006
34017
|
init_logger();
|
|
34007
|
-
|
|
34008
|
-
var
|
|
34009
|
-
var
|
|
34010
|
-
var METADATA_STORE_PATH = path.join(homedir(), ".claude-threads", "worktree-metadata.json");
|
|
34018
|
+
var log4 = createLogger("git-wt");
|
|
34019
|
+
var WORKTREES_DIR = path.join(stateHome(), ".claude-threads", "worktrees");
|
|
34020
|
+
var METADATA_STORE_PATH = path.join(stateHome(), ".claude-threads", "worktree-metadata.json");
|
|
34011
34021
|
|
|
34012
34022
|
// src/operations/post-helpers/index.ts
|
|
34013
|
-
var
|
|
34014
|
-
var sessionLog = createSessionLog(
|
|
34023
|
+
var log5 = createLogger("helpers");
|
|
34024
|
+
var sessionLog = createSessionLog(log5);
|
|
34025
|
+
|
|
34026
|
+
// src/operations/message-manager.ts
|
|
34027
|
+
init_types();
|
|
34015
34028
|
|
|
34016
34029
|
// src/operations/task-tracker.ts
|
|
34017
34030
|
var VALID_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
@@ -34144,7 +34157,7 @@ init_emoji();
|
|
|
34144
34157
|
|
|
34145
34158
|
// src/persistence/audit-log.ts
|
|
34146
34159
|
init_logger();
|
|
34147
|
-
var
|
|
34160
|
+
var log6 = createLogger("audit");
|
|
34148
34161
|
var enabledPlatforms = new Set;
|
|
34149
34162
|
var preparedDirs = new Set;
|
|
34150
34163
|
var openFds = new Map;
|
|
@@ -34157,13 +34170,13 @@ init_emoji();
|
|
|
34157
34170
|
// src/operations/executors/worktree-prompt.ts
|
|
34158
34171
|
init_emoji();
|
|
34159
34172
|
init_logger();
|
|
34160
|
-
var
|
|
34173
|
+
var log7 = createLogger("wt-prompt");
|
|
34161
34174
|
// src/operations/message-manager.ts
|
|
34162
34175
|
init_logger();
|
|
34163
34176
|
|
|
34164
34177
|
// src/transcription/elevenlabs.ts
|
|
34165
34178
|
init_logger();
|
|
34166
|
-
var
|
|
34179
|
+
var log8 = createLogger("transcribe");
|
|
34167
34180
|
|
|
34168
34181
|
// src/transcription/types.ts
|
|
34169
34182
|
var AUDIO_EXTENSIONS = new Set(["m4a", "mp3", "ogg", "opus", "wav", "aac", "flac", "webm"]);
|
|
@@ -34192,12 +34205,11 @@ function formatBytes(bytes) {
|
|
|
34192
34205
|
}
|
|
34193
34206
|
|
|
34194
34207
|
// src/operations/streaming/handler.ts
|
|
34195
|
-
var
|
|
34208
|
+
var log9 = createLogger("streaming");
|
|
34196
34209
|
// src/operations/message-manager.ts
|
|
34197
|
-
var
|
|
34210
|
+
var log10 = createLogger("msg-mgr");
|
|
34198
34211
|
// src/persistence/session-store.ts
|
|
34199
34212
|
init_logger();
|
|
34200
|
-
import { homedir as homedir2 } from "os";
|
|
34201
34213
|
import { join as join3 } from "path";
|
|
34202
34214
|
|
|
34203
34215
|
// src/sponsor.ts
|
|
@@ -34208,13 +34220,12 @@ function formatSponsorFooter(formatter) {
|
|
|
34208
34220
|
}
|
|
34209
34221
|
|
|
34210
34222
|
// src/persistence/session-store.ts
|
|
34211
|
-
var
|
|
34212
|
-
var DEFAULT_CONFIG_DIR = join3(
|
|
34223
|
+
var log11 = createLogger("persist");
|
|
34224
|
+
var DEFAULT_CONFIG_DIR = join3(stateHome(), ".config", "claude-threads");
|
|
34213
34225
|
var DEFAULT_SESSIONS_FILE = join3(DEFAULT_CONFIG_DIR, "sessions.json");
|
|
34214
34226
|
|
|
34215
34227
|
// src/config/index.ts
|
|
34216
|
-
import { resolve as
|
|
34217
|
-
import { homedir as homedir3 } from "os";
|
|
34228
|
+
import { resolve as resolve4, dirname as dirname3 } from "path";
|
|
34218
34229
|
|
|
34219
34230
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
34220
34231
|
function getDefaultExportFromCjs(x) {
|
|
@@ -37356,7 +37367,7 @@ init_types();
|
|
|
37356
37367
|
init_types();
|
|
37357
37368
|
|
|
37358
37369
|
// src/config/index.ts
|
|
37359
|
-
var CONFIG_PATH =
|
|
37370
|
+
var CONFIG_PATH = resolve4(stateHome(), ".config", "claude-threads", "config.yaml");
|
|
37360
37371
|
|
|
37361
37372
|
// src/utils/battery.ts
|
|
37362
37373
|
import { exec } from "child_process";
|
|
@@ -37365,13 +37376,13 @@ var execAsync = promisify(exec);
|
|
|
37365
37376
|
|
|
37366
37377
|
// src/changelog.ts
|
|
37367
37378
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
37368
|
-
import { dirname as dirname4, resolve as
|
|
37379
|
+
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
37369
37380
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
37370
37381
|
var __dirname3 = dirname4(fileURLToPath2(import.meta.url));
|
|
37371
37382
|
function getReleaseNotes(version) {
|
|
37372
37383
|
const possiblePaths = [
|
|
37373
|
-
|
|
37374
|
-
|
|
37384
|
+
resolve5(__dirname3, "..", "CHANGELOG.md"),
|
|
37385
|
+
resolve5(__dirname3, "..", "..", "CHANGELOG.md")
|
|
37375
37386
|
];
|
|
37376
37387
|
let changelogPath = null;
|
|
37377
37388
|
for (const p of possiblePaths) {
|
|
@@ -37465,7 +37476,7 @@ init_logger();
|
|
|
37465
37476
|
// src/utils/keep-alive.ts
|
|
37466
37477
|
init_logger();
|
|
37467
37478
|
import { spawn } from "child_process";
|
|
37468
|
-
var
|
|
37479
|
+
var log12 = createLogger("keepalive");
|
|
37469
37480
|
function keepAliveSpawnSpec(platform, parentPid) {
|
|
37470
37481
|
switch (platform) {
|
|
37471
37482
|
case "darwin":
|
|
@@ -37521,7 +37532,7 @@ class KeepAliveManager {
|
|
|
37521
37532
|
if (!enabled && this.keepAliveProcess) {
|
|
37522
37533
|
this.stopKeepAlive();
|
|
37523
37534
|
}
|
|
37524
|
-
|
|
37535
|
+
log12.debug(`Keep-alive ${enabled ? "enabled" : "disabled"}`);
|
|
37525
37536
|
}
|
|
37526
37537
|
isEnabled() {
|
|
37527
37538
|
return this.enabled;
|
|
@@ -37531,7 +37542,7 @@ class KeepAliveManager {
|
|
|
37531
37542
|
}
|
|
37532
37543
|
sessionStarted() {
|
|
37533
37544
|
this.activeSessionCount++;
|
|
37534
|
-
|
|
37545
|
+
log12.debug(`Session started (${this.activeSessionCount} active)`);
|
|
37535
37546
|
if (this.activeSessionCount === 1) {
|
|
37536
37547
|
this.startKeepAlive();
|
|
37537
37548
|
}
|
|
@@ -37540,7 +37551,7 @@ class KeepAliveManager {
|
|
|
37540
37551
|
if (this.activeSessionCount > 0) {
|
|
37541
37552
|
this.activeSessionCount--;
|
|
37542
37553
|
}
|
|
37543
|
-
|
|
37554
|
+
log12.debug(`Session ended (${this.activeSessionCount} active)`);
|
|
37544
37555
|
if (this.activeSessionCount === 0) {
|
|
37545
37556
|
this.stopKeepAlive();
|
|
37546
37557
|
}
|
|
@@ -37554,11 +37565,11 @@ class KeepAliveManager {
|
|
|
37554
37565
|
}
|
|
37555
37566
|
startKeepAlive() {
|
|
37556
37567
|
if (!this.enabled) {
|
|
37557
|
-
|
|
37568
|
+
log12.debug("Keep-alive disabled, skipping");
|
|
37558
37569
|
return;
|
|
37559
37570
|
}
|
|
37560
37571
|
if (this.keepAliveProcess) {
|
|
37561
|
-
|
|
37572
|
+
log12.debug("Keep-alive already running");
|
|
37562
37573
|
return;
|
|
37563
37574
|
}
|
|
37564
37575
|
switch (this.platform) {
|
|
@@ -37572,12 +37583,12 @@ class KeepAliveManager {
|
|
|
37572
37583
|
this.startWindowsKeepAlive();
|
|
37573
37584
|
break;
|
|
37574
37585
|
default:
|
|
37575
|
-
|
|
37586
|
+
log12.warn(`Keep-alive not supported on ${this.platform}`);
|
|
37576
37587
|
}
|
|
37577
37588
|
}
|
|
37578
37589
|
stopKeepAlive() {
|
|
37579
37590
|
if (this.keepAliveProcess) {
|
|
37580
|
-
|
|
37591
|
+
log12.debug("Stopping keep-alive");
|
|
37581
37592
|
this.keepAliveProcess.kill();
|
|
37582
37593
|
this.keepAliveProcess = null;
|
|
37583
37594
|
}
|
|
@@ -37592,18 +37603,18 @@ class KeepAliveManager {
|
|
|
37592
37603
|
detached: false
|
|
37593
37604
|
});
|
|
37594
37605
|
this.keepAliveProcess.on("error", (err) => {
|
|
37595
|
-
|
|
37606
|
+
log12.error(`Failed to start caffeinate: ${err.message}`);
|
|
37596
37607
|
this.keepAliveProcess = null;
|
|
37597
37608
|
});
|
|
37598
37609
|
this.keepAliveProcess.on("exit", (code) => {
|
|
37599
37610
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
37600
|
-
|
|
37611
|
+
log12.debug(`caffeinate exited with code ${code}`);
|
|
37601
37612
|
}
|
|
37602
37613
|
this.keepAliveProcess = null;
|
|
37603
37614
|
});
|
|
37604
|
-
|
|
37615
|
+
log12.info("Sleep prevention active (caffeinate)");
|
|
37605
37616
|
} catch (err) {
|
|
37606
|
-
|
|
37617
|
+
log12.error(`Failed to start caffeinate: ${err}`);
|
|
37607
37618
|
}
|
|
37608
37619
|
}
|
|
37609
37620
|
startLinuxKeepAlive() {
|
|
@@ -37616,19 +37627,19 @@ class KeepAliveManager {
|
|
|
37616
37627
|
detached: false
|
|
37617
37628
|
});
|
|
37618
37629
|
this.keepAliveProcess.on("error", (err) => {
|
|
37619
|
-
|
|
37630
|
+
log12.debug(`systemd-inhibit not available: ${err.message}`);
|
|
37620
37631
|
this.keepAliveProcess = null;
|
|
37621
37632
|
this.startLinuxKeepAliveFallback();
|
|
37622
37633
|
});
|
|
37623
37634
|
this.keepAliveProcess.on("exit", (code) => {
|
|
37624
37635
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
37625
|
-
|
|
37636
|
+
log12.debug(`systemd-inhibit exited with code ${code}`);
|
|
37626
37637
|
}
|
|
37627
37638
|
this.keepAliveProcess = null;
|
|
37628
37639
|
});
|
|
37629
|
-
|
|
37640
|
+
log12.info("Sleep prevention active (systemd-inhibit)");
|
|
37630
37641
|
} catch (err) {
|
|
37631
|
-
|
|
37642
|
+
log12.debug(`Failed to start systemd-inhibit: ${err}`);
|
|
37632
37643
|
this.startLinuxKeepAliveFallback();
|
|
37633
37644
|
}
|
|
37634
37645
|
}
|
|
@@ -37639,15 +37650,15 @@ class KeepAliveManager {
|
|
|
37639
37650
|
detached: false
|
|
37640
37651
|
});
|
|
37641
37652
|
this.keepAliveProcess.on("error", (err) => {
|
|
37642
|
-
|
|
37653
|
+
log12.warn(`Linux keep-alive fallback not available: ${err.message}`);
|
|
37643
37654
|
this.keepAliveProcess = null;
|
|
37644
37655
|
});
|
|
37645
37656
|
this.keepAliveProcess.on("exit", () => {
|
|
37646
37657
|
this.keepAliveProcess = null;
|
|
37647
37658
|
});
|
|
37648
|
-
|
|
37659
|
+
log12.info("Sleep prevention active (xdg-screensaver)");
|
|
37649
37660
|
} catch (err) {
|
|
37650
|
-
|
|
37661
|
+
log12.warn(`Linux keep-alive not available: ${err}`);
|
|
37651
37662
|
}
|
|
37652
37663
|
}
|
|
37653
37664
|
startWindowsKeepAlive() {
|
|
@@ -37659,25 +37670,25 @@ class KeepAliveManager {
|
|
|
37659
37670
|
windowsHide: true
|
|
37660
37671
|
});
|
|
37661
37672
|
this.keepAliveProcess.on("error", (err) => {
|
|
37662
|
-
|
|
37673
|
+
log12.warn(`Windows keep-alive not available: ${err.message}`);
|
|
37663
37674
|
this.keepAliveProcess = null;
|
|
37664
37675
|
});
|
|
37665
37676
|
this.keepAliveProcess.on("exit", (code) => {
|
|
37666
37677
|
if (code !== null && code !== 0 && this.activeSessionCount > 0) {
|
|
37667
|
-
|
|
37678
|
+
log12.debug(`PowerShell keep-alive exited with code ${code}`);
|
|
37668
37679
|
}
|
|
37669
37680
|
this.keepAliveProcess = null;
|
|
37670
37681
|
});
|
|
37671
|
-
|
|
37682
|
+
log12.info("Sleep prevention active (SetThreadExecutionState)");
|
|
37672
37683
|
} catch (err) {
|
|
37673
|
-
|
|
37684
|
+
log12.warn(`Windows keep-alive not available: ${err}`);
|
|
37674
37685
|
}
|
|
37675
37686
|
}
|
|
37676
37687
|
}
|
|
37677
37688
|
var keepAlive = new KeepAliveManager;
|
|
37678
37689
|
|
|
37679
37690
|
// src/operations/sticky-message/handler.ts
|
|
37680
|
-
var
|
|
37691
|
+
var log13 = createLogger("sticky");
|
|
37681
37692
|
var botStartedAt = new Date;
|
|
37682
37693
|
var stickyPostIds = new Map;
|
|
37683
37694
|
var needsBump = new Map;
|
|
@@ -37920,10 +37931,9 @@ init_version_check();
|
|
|
37920
37931
|
|
|
37921
37932
|
// src/persistence/thread-logger.ts
|
|
37922
37933
|
init_logger();
|
|
37923
|
-
import { homedir as homedir4 } from "os";
|
|
37924
37934
|
import { join as join4, dirname as dirname5 } from "path";
|
|
37925
|
-
var
|
|
37926
|
-
var LOGS_BASE_DIR = join4(
|
|
37935
|
+
var log14 = createLogger("thread-log");
|
|
37936
|
+
var LOGS_BASE_DIR = join4(stateHome(), ".claude-threads", "logs");
|
|
37927
37937
|
|
|
37928
37938
|
// src/operations/bug-report/handler.ts
|
|
37929
37939
|
var piiRedactor = new Redactor({ aggressive: true });
|
|
@@ -38830,11 +38840,10 @@ var sessionLog3 = createSessionLog(log21);
|
|
|
38830
38840
|
var contextPromptTimeouts = new Map;
|
|
38831
38841
|
var contextPromptFiles = new Map;
|
|
38832
38842
|
// src/memory/store.ts
|
|
38833
|
-
import { homedir as homedir6 } from "os";
|
|
38834
38843
|
import { basename as basename3, dirname as dirname6, join as join5, sep as sep2 } from "path";
|
|
38835
38844
|
init_logger();
|
|
38836
38845
|
var log22 = createLogger("memory");
|
|
38837
|
-
var DEFAULT_ROOT = join5(
|
|
38846
|
+
var DEFAULT_ROOT = join5(stateHome(), ".config", "claude-threads", "memory");
|
|
38838
38847
|
var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
|
|
38839
38848
|
|
|
38840
38849
|
// src/memory/distiller.ts
|
|
@@ -38849,9 +38858,8 @@ import { join as join7 } from "path";
|
|
|
38849
38858
|
init_logger();
|
|
38850
38859
|
|
|
38851
38860
|
// src/persistence/platform-list-store.ts
|
|
38852
|
-
import { homedir as homedir7 } from "os";
|
|
38853
38861
|
import { join as join6 } from "path";
|
|
38854
|
-
var STORES_CONFIG_DIR = join6(
|
|
38862
|
+
var STORES_CONFIG_DIR = join6(stateHome(), ".config", "claude-threads");
|
|
38855
38863
|
|
|
38856
38864
|
// src/persistence/routines-store.ts
|
|
38857
38865
|
var log24 = createLogger("routines");
|
|
@@ -38904,11 +38912,10 @@ init_logger();
|
|
|
38904
38912
|
init_quick_query();
|
|
38905
38913
|
|
|
38906
38914
|
// src/persistence/github-emails-store.ts
|
|
38907
|
-
import { homedir as homedir8 } from "os";
|
|
38908
38915
|
import { join as join9 } from "path";
|
|
38909
38916
|
init_logger();
|
|
38910
38917
|
var log32 = createLogger("gh-emails");
|
|
38911
|
-
var DEFAULT_CONFIG_DIR2 = join9(
|
|
38918
|
+
var DEFAULT_CONFIG_DIR2 = join9(stateHome(), ".config", "claude-threads");
|
|
38912
38919
|
var DEFAULT_FILE3 = join9(DEFAULT_CONFIG_DIR2, "github-emails.yaml");
|
|
38913
38920
|
|
|
38914
38921
|
// src/operations/commands/handler.ts
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -171,6 +171,7 @@ What happens: every `audio/*` attachment (or a file with an audio extension when
|
|
|
171
171
|
| `outboundFiles` | No | `send_file` settings: `{ enabled, maxBytes }` (defaults: enabled `true`, `maxBytes` 100 MB) |
|
|
172
172
|
| `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
|
|
173
173
|
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
174
|
+
| `lifecycle` | No | Session status posts — idle warning, timeout, pause, resume, and the shutdown notice a deploy leaves in each open thread: `full` (default, all of them) / `minimal` (drops the idle warning, which predicts a timeout the next message would undo anyway) / `hidden` (none). Editing a status post the thread already has is not suppressed at any level: it adds no post and no notification, and leaving a stale "session idle" up across a restart would be worse. ⚠️ An **abnormal exit** (`[Exited: <code>]`, non-zero only) posts at every level including `hidden`: it is a failure report, and silencing it would make a session that died look exactly like one that finished. Note that `hidden` also removes the post a 🔄 reaction resumes from — sending a message in the thread still resumes it, and the channel sticky says so. |
|
|
174
175
|
| `directChannelMode` | No | Direct channel mode: the whole channel is one session, and the bot replies with top-level channel posts instead of thread replies. `true` for defaults, or an options object (`respondTo`). See [Direct Channel Mode](#direct-channel-mode). |
|
|
175
176
|
| `approvals` | No | Who may answer tool-permission prompts and other reaction gates: `owner` (session participants) or `all_users` (everyone on `allowedUsers`). Unset keeps the historical default per mode — `all_users` for thread sessions, `owner` for direct channel mode. See [Approvals](#approvals). |
|
|
176
177
|
| `ackReaction` | No | Read receipt: react to every accepted message (session start, follow-up, resume) the instant it is accepted, before Claude produces output. `true` uses 👀 (`eyes`), a string names a custom emoji. Persistent, unlike the typing indicator — useful in busy channels and for messages queued behind an in-flight session start. The receipt means *accepted*, not *delivered*: a later failure (capacity limit, Claude not coming up) is still reported by its own post. `!commands` are not acked — they have their own immediate feedback, and neither are messages accepted through the message-approval flow (an authorized user approving a non-participant's message) — there the approval reaction is already the visible signal. Note: in direct channel mode this is one reaction API call per accepted message. Default off. |
|
|
@@ -195,6 +196,7 @@ What happens: every `audio/*` attachment (or a file with an audio extension when
|
|
|
195
196
|
| `outboundFiles` | No | `send_file` settings: `{ enabled, maxBytes }` (defaults: enabled `true`, `maxBytes` 100 MB) |
|
|
196
197
|
| `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
|
|
197
198
|
| `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
|
|
199
|
+
| `lifecycle` | No | Session status posts — idle warning, timeout, pause, resume, and the shutdown notice a deploy leaves in each open thread: `full` (default, all of them) / `minimal` (drops the idle warning, which predicts a timeout the next message would undo anyway) / `hidden` (none). Editing a status post the thread already has is not suppressed at any level: it adds no post and no notification, and leaving a stale "session idle" up across a restart would be worse. ⚠️ An **abnormal exit** (`[Exited: <code>]`, non-zero only) posts at every level including `hidden`: it is a failure report, and silencing it would make a session that died look exactly like one that finished. Note that `hidden` also removes the post a 🔄 reaction resumes from — sending a message in the thread still resumes it, and the channel sticky says so. |
|
|
198
200
|
| `directChannelMode` | No | Direct channel mode: the whole channel is one session, and the bot replies with top-level channel posts instead of thread replies. `true` for defaults, or an options object (`respondTo`). See [Direct Channel Mode](#direct-channel-mode). |
|
|
199
201
|
| `approvals` | No | Who may answer tool-permission prompts and other reaction gates: `owner` (session participants) or `all_users` (everyone on `allowedUsers`). Unset keeps the historical default per mode — `all_users` for thread sessions, `owner` for direct channel mode. See [Approvals](#approvals). |
|
|
200
202
|
| `ackReaction` | No | Read receipt: react to every accepted message (session start, follow-up, resume) the instant it is accepted, before Claude produces output. `true` uses 👀 (`eyes`), a string names a custom emoji. Persistent, unlike the typing indicator — useful in busy channels and for messages queued behind an in-flight session start. The receipt means *accepted*, not *delivered*: a later failure (capacity limit, Claude not coming up) is still reported by its own post. `!commands` are not acked — they have their own immediate feedback, and neither are messages accepted through the message-approval flow (an authorized user approving a non-participant's message) — there the approval reaction is already the visible signal. Note: in direct channel mode this is one reaction API call per accepted message. Default off. |
|
|
@@ -369,6 +371,30 @@ platforms:
|
|
|
369
371
|
|
|
370
372
|
Note: the per-platform `stickyMessage: <mode>` field is distinct from the top-level `Config.stickyMessage: { description, footer }` block, which still customizes the full sticky for platforms not in `hidden` mode.
|
|
371
373
|
|
|
374
|
+
#### End-of-turn marker
|
|
375
|
+
|
|
376
|
+
Integrations that read the channel (a voice front end, a phone bridge, a dashboard) need to know when Claude's answer is complete. The daemon streams by editing one post, so without help they can only guess from the text going quiet. With a marker, the daemon stamps the turn's last reply post the moment the turn ends:
|
|
377
|
+
|
|
378
|
+
```yaml
|
|
379
|
+
platforms:
|
|
380
|
+
- id: slack-main
|
|
381
|
+
type: slack
|
|
382
|
+
turnMarker: metadata # reaction | metadata | off (default)
|
|
383
|
+
# turnMarkerEmoji: checkered_flag # reaction only; this is the default
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
| `turnMarker` | What happens | Who sees it |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `metadata` | the last reply post gets Slack message metadata `event_type: claude_threads_turn_complete`, `event_payload: { v, session, turn, ok }` | integrations reading `conversations.history` with `include_all_metadata=true`; invisible in the UI. Slack only |
|
|
389
|
+
| `reaction` | the bot reacts on the last reply post with `turnMarkerEmoji` | everyone. Slack and Mattermost |
|
|
390
|
+
| `off` | nothing | |
|
|
391
|
+
|
|
392
|
+
**Format stability.** The marker is a published integration contract, not an internal detail. Within a 1.x release line, `event_type` will not change, and `event_payload` will only gain fields — never lose one or change the meaning of one. `v` is the payload version; a reader should ignore fields it does not recognize and treat an unknown `v` as "marker present, payload unread". `session` is `platformId:threadId` and is stable across bot restarts and Claude respawns. `turn` counts turns within one bot process: it restarts at 1 after a bot restart and is an ordering hint within a single run, never a unique key. Readers that need exactly-once should dedupe by post id.
|
|
393
|
+
|
|
394
|
+
`ok` is false when the turn ended with an error. A turn with no reply post marks nothing. A marker failure never touches the reply.
|
|
395
|
+
|
|
396
|
+
Each marked turn costs one extra API call (a `chat.update` or `reactions.add`) beyond the streaming writes.
|
|
397
|
+
|
|
372
398
|
### Memory (`memory`, default: fully enabled)
|
|
373
399
|
|
|
374
400
|
Each platform instance (≈ one channel) can carry persistent memory, modeled on
|
|
@@ -663,6 +689,7 @@ Exactly one of `home` or `apiKey` should be set per account. Persisted sessions
|
|
|
663
689
|
| `CLAUDE_PATH` | Path to the `claude` binary. Overrides the PATH lookup and the common install locations. | `claude` (from PATH) |
|
|
664
690
|
| `DECISION_BRIDGE_TIMEOUT_MS` | How long the MCP permission server waits for a plan approval or question answer routed through the decision bridge (the bot's reaction UI) before falling back to the legacy behavior (generic prompt for plans, auto-allow for questions). | `3600000` (1 h) |
|
|
665
691
|
| `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | Strip `ANTHROPIC_*`, `AWS_*_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, `GOOGLE_APPLICATION_CREDENTIALS`, and similar from Bash, hook, and stdio-MCP subprocesses Claude spawns. Bot-specific vars like `PLATFORM_TOKEN` pass through. **Also forces permission mode to `default`**; `--dangerously-skip-permissions` will be rejected. Requires Claude CLI 2.1.83+. | - |
|
|
692
|
+
| `CLAUDE_THREADS_HOME` | Root under which the bot keeps its state (`<root>/.config/claude-threads`, `<root>/.claude-threads`): config, sessions, logs, audit, memory, worktrees, update state. Lets two bots run under the same user without sharing any of it. One process per root; a second start against the same root is refused. | `$HOME` |
|
|
666
693
|
| `CLAUDE_THREADS_SESSIONS_PATH` | Override the path to the persisted sessions file (default `~/.config/claude-threads/sessions.json`). | - |
|
|
667
694
|
| `CLAUDE_THREADS_GITHUB_EMAILS_PATH` | Override the path to the GitHub-emails store used for commit attribution. | - |
|
|
668
695
|
| `CLAUDE_THREADS_MEMORY_DIR` | Override the root of the persistent memory storage (default `~/.config/claude-threads/memory/`). | - |
|
|
@@ -37,6 +37,9 @@ ExecStart=/usr/bin/env claude-threads-daemon
|
|
|
37
37
|
# This is a fallback for unexpected crashes
|
|
38
38
|
Restart=on-failure
|
|
39
39
|
RestartSec=10
|
|
40
|
+
# Exit 3 = another instance holds the state directory (see CLAUDE_THREADS_HOME);
|
|
41
|
+
# restarting would only collide again.
|
|
42
|
+
RestartPreventExitStatus=3
|
|
40
43
|
|
|
41
44
|
# Resource limits (adjust as needed)
|
|
42
45
|
# LimitNOFILE=65536
|