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/dist/index.js CHANGED
@@ -2128,6 +2128,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2128
2128
  exports.useColor = useColor;
2129
2129
  });
2130
2130
 
2131
+ // src/utils/state-home.ts
2132
+ import { homedir } from "os";
2133
+ import { resolve } from "path";
2134
+ function stateHome() {
2135
+ return resolve(process.env.CLAUDE_THREADS_HOME || homedir());
2136
+ }
2137
+ function hasStateHomeOverride() {
2138
+ return stateHome() !== resolve(homedir());
2139
+ }
2140
+ var init_state_home = () => {};
2141
+
2131
2142
  // src/utils/logger.ts
2132
2143
  function setLogHandler(handler) {
2133
2144
  globalLogHandler = handler;
@@ -2221,6 +2232,23 @@ function resolveOverheadVisibility(value, fieldPath) {
2221
2232
  return value;
2222
2233
  throw new Error(`Invalid ${fieldPath}: expected one of ${OVERHEAD_VISIBILITY_VALUES.join(", ")}, got ${JSON.stringify(value)}`);
2223
2234
  }
2235
+ function resolveTurnMarker(mode, emoji, platformType, fieldPath) {
2236
+ const m = mode === undefined || mode === null ? "off" : mode;
2237
+ if (!TURN_MARKER_VALUES.includes(m)) {
2238
+ throw new Error(`Invalid ${fieldPath}.turnMarker: expected one of ${TURN_MARKER_VALUES.join(", ")}, got ${JSON.stringify(mode)}`);
2239
+ }
2240
+ if (m === "metadata" && platformType !== "slack") {
2241
+ throw new Error(`Invalid ${fieldPath}.turnMarker: metadata is a Slack feature; use reaction on ${platformType}`);
2242
+ }
2243
+ if (emoji !== undefined && emoji !== null) {
2244
+ if (typeof emoji !== "string" || !/^[a-z0-9_+-]+$/.test(emoji)) {
2245
+ throw new Error(`Invalid ${fieldPath}.turnMarkerEmoji: expected an emoji name like checkered_flag, got ${JSON.stringify(emoji)}`);
2246
+ }
2247
+ }
2248
+ if (m === "reaction")
2249
+ return { mode: "reaction", emoji: emoji ?? DEFAULT_TURN_MARKER_EMOJI };
2250
+ return { mode: m };
2251
+ }
2224
2252
  function resolveMemoryConfig(value, fieldPath) {
2225
2253
  if (value === undefined || value === null || value === true)
2226
2254
  return DEFAULT_MEMORY_CONFIG;
@@ -2395,10 +2423,12 @@ function effectivePermissionMode(input) {
2395
2423
  return "default";
2396
2424
  return input.botWideMode;
2397
2425
  }
2398
- var OVERHEAD_VISIBILITY_VALUES, DEFAULT_OVERHEAD_VISIBILITY = "full", RECONNECT_POLICY_VALUES, DEFAULT_RECONNECT_POLICY = "retry", DEFAULT_MEMORY_CONFIG, MEMORY_DISABLED, BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS, LIMITS_DEFAULTS, MODE_INFO;
2426
+ var OVERHEAD_VISIBILITY_VALUES, DEFAULT_OVERHEAD_VISIBILITY = "full", RECONNECT_POLICY_VALUES, DEFAULT_RECONNECT_POLICY = "retry", TURN_MARKER_VALUES, DEFAULT_TURN_MARKER, DEFAULT_TURN_MARKER_EMOJI = "checkered_flag", TURN_COMPLETE_EVENT_TYPE = "claude_threads_turn_complete", TURN_COMPLETE_PAYLOAD_VERSION = 1, DEFAULT_MEMORY_CONFIG, MEMORY_DISABLED, BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS, LIMITS_DEFAULTS, MODE_INFO;
2399
2427
  var init_types = __esm(() => {
2400
2428
  OVERHEAD_VISIBILITY_VALUES = ["full", "minimal", "hidden"];
2401
2429
  RECONNECT_POLICY_VALUES = ["retry", "exit"];
2430
+ TURN_MARKER_VALUES = ["reaction", "metadata", "off"];
2431
+ DEFAULT_TURN_MARKER = { mode: "off" };
2402
2432
  DEFAULT_MEMORY_CONFIG = {
2403
2433
  enabled: true,
2404
2434
  repoLayer: true,
@@ -4597,7 +4627,7 @@ var init_rate_limit_detector = __esm(() => {
4597
4627
 
4598
4628
  // src/claude/cli.ts
4599
4629
  import { EventEmitter } from "events";
4600
- import { resolve as resolve2, dirname as dirname2 } from "path";
4630
+ import { resolve as resolve3, dirname as dirname2 } from "path";
4601
4631
  import { fileURLToPath } from "url";
4602
4632
  import { existsSync as existsSync4, readFileSync as readFileSync2, watchFile, unwatchFile, unlinkSync, statSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
4603
4633
  import { tmpdir as tmpdir2 } from "os";
@@ -5139,15 +5169,15 @@ var init_cli = __esm(() => {
5139
5169
  getMcpServerPath() {
5140
5170
  const __filename2 = fileURLToPath(import.meta.url);
5141
5171
  const __dirname2 = dirname2(__filename2);
5142
- const bundledPath = resolve2(__dirname2, "mcp", "mcp-server.js");
5172
+ const bundledPath = resolve3(__dirname2, "mcp", "mcp-server.js");
5143
5173
  if (existsSync4(bundledPath)) {
5144
5174
  return bundledPath;
5145
5175
  }
5146
- const sourceLayoutPath = resolve2(__dirname2, "..", "mcp", "mcp-server.js");
5176
+ const sourceLayoutPath = resolve3(__dirname2, "..", "mcp", "mcp-server.js");
5147
5177
  if (existsSync4(sourceLayoutPath)) {
5148
5178
  return sourceLayoutPath;
5149
5179
  }
5150
- const tsPath = resolve2(__dirname2, "..", "mcp", "mcp-server.ts");
5180
+ const tsPath = resolve3(__dirname2, "..", "mcp", "mcp-server.ts");
5151
5181
  if (existsSync4(tsPath)) {
5152
5182
  return tsPath;
5153
5183
  }
@@ -5156,15 +5186,15 @@ var init_cli = __esm(() => {
5156
5186
  getStatusLineWriterPath() {
5157
5187
  const __filename2 = fileURLToPath(import.meta.url);
5158
5188
  const __dirname2 = dirname2(__filename2);
5159
- const bundledPath = resolve2(__dirname2, "statusline", "writer.js");
5189
+ const bundledPath = resolve3(__dirname2, "statusline", "writer.js");
5160
5190
  if (existsSync4(bundledPath)) {
5161
5191
  return bundledPath;
5162
5192
  }
5163
- const sourceLayoutPath = resolve2(__dirname2, "..", "statusline", "writer.js");
5193
+ const sourceLayoutPath = resolve3(__dirname2, "..", "statusline", "writer.js");
5164
5194
  if (existsSync4(sourceLayoutPath)) {
5165
5195
  return sourceLayoutPath;
5166
5196
  }
5167
- const tsPath = resolve2(__dirname2, "..", "statusline", "writer.ts");
5197
+ const tsPath = resolve3(__dirname2, "..", "statusline", "writer.ts");
5168
5198
  if (existsSync4(tsPath)) {
5169
5199
  return tsPath;
5170
5200
  }
@@ -5411,7 +5441,7 @@ var init_render = __esm(() => {
5411
5441
  });
5412
5442
 
5413
5443
  // src/usage/index.ts
5414
- import { homedir as homedir5 } from "os";
5444
+ import { homedir as homedir2 } from "os";
5415
5445
  import path3 from "path";
5416
5446
  function toLimits(usage) {
5417
5447
  const limits = [
@@ -5461,7 +5491,7 @@ async function collectUsage(options) {
5461
5491
  }
5462
5492
  return results;
5463
5493
  }
5464
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path3.join(homedir5(), ".claude");
5494
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path3.join(homedir2(), ".claude");
5465
5495
  return [await readSeat(profileNameFor(configDir), configDir)];
5466
5496
  }
5467
5497
  var USAGE_PROBE_TIMEOUT_MS = 1e4;
@@ -5525,7 +5555,6 @@ var init_emoji = __esm(() => {
5525
5555
  import { randomUUID as randomUUID2 } from "crypto";
5526
5556
  import * as path4 from "path";
5527
5557
  import * as fs from "fs/promises";
5528
- import { homedir as homedir6 } from "os";
5529
5558
  async function execGit(args, cwd) {
5530
5559
  const cmd = `git ${args.join(" ")}`;
5531
5560
  log10.debug(`Executing: ${cmd}`);
@@ -5834,10 +5863,11 @@ async function removeWorktreeMetadata(worktreePath) {
5834
5863
  var log10, WORKTREES_DIR, METADATA_STORE_PATH;
5835
5864
  var init_worktree = __esm(() => {
5836
5865
  init_spawn();
5866
+ init_state_home();
5837
5867
  init_logger();
5838
5868
  log10 = createLogger("git-wt");
5839
- WORKTREES_DIR = path4.join(homedir6(), ".claude-threads", "worktrees");
5840
- METADATA_STORE_PATH = path4.join(homedir6(), ".claude-threads", "worktree-metadata.json");
5869
+ WORKTREES_DIR = path4.join(stateHome(), ".claude-threads", "worktrees");
5870
+ METADATA_STORE_PATH = path4.join(stateHome(), ".claude-threads", "worktree-metadata.json");
5841
5871
  });
5842
5872
 
5843
5873
  // node_modules/graceful-fs/polyfills.js
@@ -47494,17 +47524,17 @@ function resolveAckReaction(configured) {
47494
47524
  }
47495
47525
 
47496
47526
  // src/persistence/audit-log.ts
47527
+ init_state_home();
47497
47528
  init_logger();
47498
47529
  import { chmodSync, closeSync, constants as fsConstants, fchmodSync, lstatSync, mkdirSync, openSync, writeSync } from "fs";
47499
47530
  import { join } from "path";
47500
- import { homedir } from "os";
47501
47531
  var log = createLogger("audit");
47502
47532
  var DETAIL_MAX = 500;
47503
47533
  var enabledPlatforms = new Set;
47504
47534
  var preparedDirs = new Set;
47505
47535
  var openFds = new Map;
47506
47536
  function auditDir() {
47507
- return process.env.CLAUDE_THREADS_AUDIT_DIR || join(homedir(), ".claude-threads", "audit");
47537
+ return process.env.CLAUDE_THREADS_AUDIT_DIR || join(stateHome(), ".claude-threads", "audit");
47508
47538
  }
47509
47539
  function configureAuditLog(platformId, enabled) {
47510
47540
  if (enabled)
@@ -47612,6 +47642,20 @@ function deriveDmPlatformConfig(parent, channelId, partnerUsernames) {
47612
47642
  };
47613
47643
  }
47614
47644
 
47645
+ // src/session/lifecycle-visibility.ts
47646
+ function shouldPostLifecycle(visibility, kind) {
47647
+ if (kind === "abnormal-exit")
47648
+ return true;
47649
+ switch (visibility) {
47650
+ case "full":
47651
+ return true;
47652
+ case "minimal":
47653
+ return kind !== "idle-warning";
47654
+ case "hidden":
47655
+ return false;
47656
+ }
47657
+ }
47658
+
47615
47659
  // src/session/lifecycle-fsm.ts
47616
47660
  init_logger();
47617
47661
  var log2 = createLogger("fsm");
@@ -47718,9 +47762,9 @@ function getSessionStatus(session) {
47718
47762
  }
47719
47763
 
47720
47764
  // src/config/index.ts
47765
+ init_state_home();
47721
47766
  import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "fs";
47722
- import { resolve, dirname } from "path";
47723
- import { homedir as homedir2 } from "os";
47767
+ import { resolve as resolve2, dirname } from "path";
47724
47768
 
47725
47769
  // node_modules/js-yaml/dist/js-yaml.mjs
47726
47770
  function getDefaultExportFromCjs(x) {
@@ -50889,7 +50933,7 @@ function resolvePlatformMcpPosture(platforms, globalMcpServers, managedPresent =
50889
50933
  }
50890
50934
 
50891
50935
  // src/config/index.ts
50892
- var CONFIG_PATH = resolve(homedir2(), ".config", "claude-threads", "config.yaml");
50936
+ var CONFIG_PATH = resolve2(stateHome(), ".config", "claude-threads", "config.yaml");
50893
50937
  function loadConfigWithMigration() {
50894
50938
  if (existsSync2(CONFIG_PATH)) {
50895
50939
  const content = readFileSync(CONFIG_PATH, "utf-8");
@@ -51072,7 +51116,6 @@ function writeFileAtomic(file, content) {
51072
51116
 
51073
51117
  // src/persistence/session-store.ts
51074
51118
  init_logger();
51075
- import { homedir as homedir3 } from "os";
51076
51119
  import { join as join5 } from "path";
51077
51120
 
51078
51121
  // src/sponsor.ts
@@ -51097,6 +51140,7 @@ function formatMilestoneLine(formatter, milestone) {
51097
51140
  }
51098
51141
 
51099
51142
  // src/persistence/session-store.ts
51143
+ init_state_home();
51100
51144
  var log4 = createLogger("persist");
51101
51145
  function resolveEndReason(session) {
51102
51146
  if (!session.cleanedAt)
@@ -51107,7 +51151,7 @@ function isRevivable(session) {
51107
51151
  return resolveEndReason(session) !== "stopped";
51108
51152
  }
51109
51153
  var STORE_VERSION = 2;
51110
- var DEFAULT_CONFIG_DIR = join5(homedir3(), ".config", "claude-threads");
51154
+ var DEFAULT_CONFIG_DIR = join5(stateHome(), ".config", "claude-threads");
51111
51155
  var DEFAULT_SESSIONS_FILE = join5(DEFAULT_CONFIG_DIR, "sessions.json");
51112
51156
 
51113
51157
  class SessionStore {
@@ -51384,12 +51428,12 @@ class SessionStore {
51384
51428
  }
51385
51429
 
51386
51430
  // src/persistence/thread-logger.ts
51431
+ init_state_home();
51387
51432
  init_logger();
51388
51433
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, appendFileSync, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, rmdirSync, readFileSync as readFileSync4, chmodSync as chmodSync4 } from "fs";
51389
- import { homedir as homedir4 } from "os";
51390
51434
  import { join as join6, dirname as dirname3 } from "path";
51391
51435
  var log5 = createLogger("thread-log");
51392
- var LOGS_BASE_DIR = join6(homedir4(), ".claude-threads", "logs");
51436
+ var LOGS_BASE_DIR = join6(stateHome(), ".claude-threads", "logs");
51393
51437
 
51394
51438
  class ThreadLoggerImpl {
51395
51439
  platformId;
@@ -51661,14 +51705,14 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
51661
51705
 
51662
51706
  // src/version.ts
51663
51707
  import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
51664
- import { dirname as dirname4, resolve as resolve3 } from "path";
51708
+ import { dirname as dirname4, resolve as resolve4 } from "path";
51665
51709
  import { fileURLToPath as fileURLToPath2 } from "url";
51666
51710
  var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
51667
51711
  function loadPackageJson() {
51668
51712
  const candidates = [
51669
- resolve3(__dirname2, "..", "package.json"),
51670
- resolve3(__dirname2, "..", "..", "package.json"),
51671
- resolve3(process.cwd(), "package.json")
51713
+ resolve4(__dirname2, "..", "package.json"),
51714
+ resolve4(__dirname2, "..", "..", "package.json"),
51715
+ resolve4(process.cwd(), "package.json")
51672
51716
  ];
51673
51717
  for (const candidate of candidates) {
51674
51718
  if (existsSync7(candidate)) {
@@ -52136,13 +52180,13 @@ ${formatter.formatBold("Reactions:")}
52136
52180
 
52137
52181
  // src/changelog.ts
52138
52182
  import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
52139
- import { dirname as dirname5, resolve as resolve4 } from "path";
52183
+ import { dirname as dirname5, resolve as resolve5 } from "path";
52140
52184
  import { fileURLToPath as fileURLToPath3 } from "url";
52141
52185
  var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
52142
52186
  function getReleaseNotes(version) {
52143
52187
  const possiblePaths = [
52144
- resolve4(__dirname3, "..", "CHANGELOG.md"),
52145
- resolve4(__dirname3, "..", "..", "CHANGELOG.md")
52188
+ resolve5(__dirname3, "..", "CHANGELOG.md"),
52189
+ resolve5(__dirname3, "..", "..", "CHANGELOG.md")
52146
52190
  ];
52147
52191
  let changelogPath = null;
52148
52192
  for (const p of possiblePaths) {
@@ -53332,6 +53376,8 @@ function updateLastMessage(session, post) {
53332
53376
  init_cli();
53333
53377
 
53334
53378
  // src/operations/streaming/handler.ts
53379
+ init_state_home();
53380
+ import { createHash } from "crypto";
53335
53381
  import { lstat, mkdir as mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
53336
53382
  import { tmpdir as tmpdir3 } from "os";
53337
53383
  import { join as join8 } from "path";
@@ -53468,7 +53514,12 @@ function safeIdSegment(id) {
53468
53514
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
53469
53515
  }
53470
53516
  function getSessionUploadDir(platformId, threadId) {
53471
- return join8(tmpdir3(), UPLOAD_ROOT_DIR, `${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
53517
+ return join8(tmpdir3(), UPLOAD_ROOT_DIR, `${instancePrefix()}${safeIdSegment(platformId)}-${safeIdSegment(threadId)}`);
53518
+ }
53519
+ function instancePrefix() {
53520
+ if (!hasStateHomeOverride())
53521
+ return "";
53522
+ return createHash("sha1").update(stateHome()).digest("hex").slice(0, 12) + "-";
53472
53523
  }
53473
53524
  async function cleanupSessionUploads(platformId, threadId) {
53474
53525
  if (!platformId || !threadId)
@@ -53676,7 +53727,7 @@ function buildRestartCliOptions(session, ctx) {
53676
53727
 
53677
53728
  // src/operations/commands/handler.ts
53678
53729
  import { randomUUID as randomUUID6 } from "crypto";
53679
- import { resolve as resolve6 } from "path";
53730
+ import { resolve as resolve7 } from "path";
53680
53731
  import { existsSync as existsSync12, statSync as statSync4 } from "fs";
53681
53732
 
53682
53733
  // node_modules/update-notifier/update-notifier.js
@@ -57686,8 +57737,8 @@ function checkGitHubCli(exec = execSync2) {
57686
57737
  }
57687
57738
  return { installed: true, authenticated: true };
57688
57739
  }
57689
- async function createGitHubIssue(title, body, workingDir) {
57690
- const ghStatus = checkGitHubCli();
57740
+ async function createGitHubIssue(title, body, workingDir, exec = execSync2) {
57741
+ const ghStatus = checkGitHubCli(exec);
57691
57742
  if (!ghStatus.installed || !ghStatus.authenticated) {
57692
57743
  throw new Error(ghStatus.error);
57693
57744
  }
@@ -57695,7 +57746,7 @@ async function createGitHubIssue(title, body, workingDir) {
57695
57746
  try {
57696
57747
  writeFileSync5(bodyFile, body, "utf-8");
57697
57748
  const cmd = `gh issue create --repo "${GITHUB_REPO}" --title "${escapeShell(title)}" --body-file "${bodyFile}"`;
57698
- const result = execSync2(cmd, {
57749
+ const result = exec(cmd, {
57699
57750
  cwd: workingDir,
57700
57751
  encoding: "utf-8",
57701
57752
  timeout: 30000,
@@ -59627,12 +59678,13 @@ function createAppendContentOp(sessionId, content, isToolOutput) {
59627
59678
  isToolOutput
59628
59679
  };
59629
59680
  }
59630
- function createFlushOp(sessionId, reason) {
59681
+ function createFlushOp(sessionId, reason, resultOk) {
59631
59682
  return {
59632
59683
  type: "flush",
59633
59684
  sessionId,
59634
59685
  timestamp: Date.now(),
59635
- reason
59686
+ reason,
59687
+ ...resultOk === undefined ? {} : { resultOk }
59636
59688
  };
59637
59689
  }
59638
59690
  function createTaskListOp(sessionId, action, tasks, toolUseId) {
@@ -59687,6 +59739,7 @@ function createStatusUpdateOp(sessionId, options) {
59687
59739
  };
59688
59740
  }
59689
59741
  // src/operations/transformer.ts
59742
+ init_cli();
59690
59743
  function transformEvent(event, ctx) {
59691
59744
  if (event.parent_tool_use_id) {
59692
59745
  return [];
@@ -59849,7 +59902,7 @@ function transformToolResult(event, ctx) {
59849
59902
  }
59850
59903
  function transformResult(event, ctx) {
59851
59904
  const operations = [];
59852
- operations.push(createFlushOp(ctx.sessionId, "result"));
59905
+ operations.push(createFlushOp(ctx.sessionId, "result", !isErrorResultEvent(event)));
59853
59906
  const result = event;
59854
59907
  const r = result.result;
59855
59908
  operations.push(createStatusUpdateOp(ctx.sessionId, {
@@ -59932,6 +59985,9 @@ function truncateAtWord2(text, maxLength) {
59932
59985
  }
59933
59986
  return truncated + "...";
59934
59987
  }
59988
+ // src/operations/message-manager.ts
59989
+ init_types();
59990
+
59935
59991
  // src/operations/task-tracker.ts
59936
59992
  var VALID_STATUSES = new Set(["pending", "in_progress", "completed"]);
59937
59993
  var CREATED_RESULT_RE = /Task #(\S+) created/;
@@ -61891,6 +61947,9 @@ class MessageManager {
61891
61947
  platform;
61892
61948
  postTracker;
61893
61949
  contentBreaker;
61950
+ turnMarker;
61951
+ flushInFlight = null;
61952
+ turn = 0;
61894
61953
  session;
61895
61954
  contentExecutor;
61896
61955
  taskListExecutor;
@@ -61933,6 +61992,7 @@ class MessageManager {
61933
61992
  this.startTypingCallback = options.startTyping;
61934
61993
  this.emitSessionUpdateCallback = options.emitSessionUpdate;
61935
61994
  this.flushDelayMs = options.flushDelayMs ?? MessageManager.DEFAULT_FLUSH_DELAY_MS;
61995
+ this.turnMarker = options.turnMarker ?? { mode: "off" };
61936
61996
  this.events = createMessageManagerEvents();
61937
61997
  this.contentBreaker = new DefaultContentBreaker;
61938
61998
  this.contentExecutor = new ContentExecutor({
@@ -62074,15 +62134,58 @@ class MessageManager {
62074
62134
  }
62075
62135
  async handleFlushOp(op, ctx) {
62076
62136
  this.cancelScheduledFlush();
62077
- await this.contentExecutor.executeFlush(op, ctx);
62137
+ if (this.flushInFlight)
62138
+ await this.flushInFlight.catch(() => {
62139
+ return;
62140
+ });
62141
+ if (op.reason === "result")
62142
+ this.turn++;
62143
+ await this.runTrackedFlush(op, ctx);
62144
+ if (op.reason === "result") {
62145
+ await this.markTurnComplete(ctx, op.resultOk !== false);
62146
+ }
62147
+ }
62148
+ async markTurnComplete(ctx, ok) {
62149
+ if (this.turnMarker.mode === "off")
62150
+ return;
62151
+ const { currentPostId, currentPostContent } = this.contentExecutor.getState();
62152
+ if (!currentPostId)
62153
+ return;
62154
+ try {
62155
+ if (this.turnMarker.mode === "metadata") {
62156
+ await this.platform.updatePost(currentPostId, currentPostContent, {
62157
+ metadata: {
62158
+ event_type: TURN_COMPLETE_EVENT_TYPE,
62159
+ event_payload: { v: TURN_COMPLETE_PAYLOAD_VERSION, session: this.sessionId, turn: this.turn, ok }
62160
+ }
62161
+ });
62162
+ } else {
62163
+ await this.platform.addReaction(currentPostId, this.turnMarker.emoji ?? "checkered_flag");
62164
+ }
62165
+ } catch (err) {
62166
+ const message = err.message ?? String(err);
62167
+ if (this.turnMarker.mode === "reaction" && message.includes("already_reacted"))
62168
+ return;
62169
+ ctx.logger.warn(`turn marker (${this.turnMarker.mode}) failed on ${currentPostId}: ${message}`);
62170
+ }
62171
+ }
62172
+ async runTrackedFlush(op, ctx) {
62173
+ const running = this.contentExecutor.executeFlush(op, ctx).finally(() => {
62174
+ if (this.flushInFlight === running)
62175
+ this.flushInFlight = null;
62176
+ });
62177
+ this.flushInFlight = running;
62178
+ await running;
62078
62179
  }
62079
62180
  scheduleFlush(ctx) {
62080
62181
  if (this.flushTimer)
62081
62182
  return;
62082
- this.flushTimer = setTimeout(async () => {
62183
+ this.flushTimer = setTimeout(() => {
62083
62184
  this.flushTimer = null;
62084
62185
  const flushOp = createFlushOp(this.sessionId, "soft_threshold");
62085
- await this.contentExecutor.executeFlush(flushOp, ctx);
62186
+ this.runTrackedFlush(flushOp, ctx).catch(() => {
62187
+ return;
62188
+ });
62086
62189
  }, this.flushDelayMs);
62087
62190
  }
62088
62191
  cancelScheduledFlush() {
@@ -62093,8 +62196,12 @@ class MessageManager {
62093
62196
  }
62094
62197
  async flush() {
62095
62198
  this.cancelScheduledFlush();
62199
+ if (this.flushInFlight)
62200
+ await this.flushInFlight.catch(() => {
62201
+ return;
62202
+ });
62096
62203
  const flushOp = createFlushOp(this.sessionId, "explicit");
62097
- await this.contentExecutor.executeFlush(flushOp, this.getExecutorContext());
62204
+ await this.runTrackedFlush(flushOp, this.getExecutorContext());
62098
62205
  }
62099
62206
  getExecutorContext() {
62100
62207
  return {
@@ -62639,12 +62746,12 @@ function formatHistoryEntry(session, formatter, getThreadLink) {
62639
62746
  const topic = getHistorySessionTopic(session, formatter);
62640
62747
  const threadLink = formatter.formatLink(topic, getThreadLink(session.threadId));
62641
62748
  const displayName = session.startedByDisplayName || session.startedBy;
62642
- const isTimedOut = isRevivable(session) && session.lifecyclePostId;
62749
+ const isTimedOut = isRevivable(session);
62643
62750
  const lastActivity = new Date(session.lastActivityAt);
62644
62751
  const time = formatRelativeTimeShort(lastActivity);
62645
62752
  const prStr = session.pullRequestUrl ? ` · ${formatPullRequestLink(session.pullRequestUrl, formatter)}` : "";
62646
62753
  const indicator = isTimedOut ? "⏸️" : "✓";
62647
- const resumeHint = isTimedOut ? ` · ${formatter.formatItalic("react \uD83D\uDD04 to resume")}` : "";
62754
+ const resumeHint = isTimedOut ? ` · ${formatter.formatItalic(session.lifecyclePostId ? "react \uD83D\uDD04 to resume" : "send a message to resume")}` : "";
62648
62755
  const lines = [];
62649
62756
  lines.push(` ${indicator} ${threadLink} · ${formatter.formatBold(displayName)}${prStr} · ${time}${resumeHint}`);
62650
62757
  if (session.sessionDescription) {
@@ -63101,19 +63208,19 @@ async function cleanupOldStickyMessages(platform, botUserId, forceRun = false, e
63101
63208
  }
63102
63209
  }
63103
63210
  // src/memory/store.ts
63104
- import { createHash } from "crypto";
63211
+ init_state_home();
63212
+ import { createHash as createHash2 } from "crypto";
63105
63213
  import {
63106
63214
  existsSync as existsSync9,
63107
63215
  mkdirSync as mkdirSync5,
63108
63216
  readFileSync as readFileSync7,
63109
63217
  realpathSync
63110
63218
  } from "fs";
63111
- import { homedir as homedir7 } from "os";
63112
63219
  import { basename as basename3, dirname as dirname7, join as join10, sep as sep2 } from "path";
63113
63220
  init_logger();
63114
63221
  init_worktree();
63115
63222
  var log18 = createLogger("memory");
63116
- var DEFAULT_ROOT = join10(homedir7(), ".config", "claude-threads", "memory");
63223
+ var DEFAULT_ROOT = join10(stateHome(), ".config", "claude-threads", "memory");
63117
63224
  var CHANNEL_BLOCK_MAX_LINES = 200;
63118
63225
  var CHANNEL_BLOCK_MAX_BYTES = 25 * 1024;
63119
63226
  var CHANNEL_FILE_MAX_ENTRIES = 400;
@@ -63124,7 +63231,7 @@ function safeIdSegment2(id) {
63124
63231
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
63125
63232
  }
63126
63233
  function shortHash(value, length) {
63127
- return createHash("sha256").update(value).digest("hex").slice(0, length);
63234
+ return createHash2("sha256").update(value).digest("hex").slice(0, length);
63128
63235
  }
63129
63236
  function platformSegment(platformId) {
63130
63237
  return `${safeIdSegment2(platformId) || "platform"}-${shortHash(platformId, 6)}`;
@@ -63504,10 +63611,10 @@ import { join as join12 } from "path";
63504
63611
  import { randomUUID as randomUUID3 } from "crypto";
63505
63612
 
63506
63613
  // src/persistence/platform-list-store.ts
63614
+ init_state_home();
63507
63615
  import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
63508
- import { homedir as homedir8 } from "os";
63509
63616
  import { join as join11 } from "path";
63510
- var STORES_CONFIG_DIR = join11(homedir8(), ".config", "claude-threads");
63617
+ var STORES_CONFIG_DIR = join11(stateHome(), ".config", "claude-threads");
63511
63618
  var STORE_VERSION2 = 1;
63512
63619
 
63513
63620
  class PlatformListStore {
@@ -65695,12 +65802,12 @@ async function suggestSessionMetadata(context) {
65695
65802
  init_quick_query();
65696
65803
 
65697
65804
  // src/persistence/github-emails-store.ts
65805
+ init_state_home();
65698
65806
  import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync9 } from "fs";
65699
- import { homedir as homedir9 } from "os";
65700
65807
  import { join as join14 } from "path";
65701
65808
  init_logger();
65702
65809
  var log33 = createLogger("gh-emails");
65703
- var DEFAULT_CONFIG_DIR2 = join14(homedir9(), ".config", "claude-threads");
65810
+ var DEFAULT_CONFIG_DIR2 = join14(stateHome(), ".config", "claude-threads");
65704
65811
  var DEFAULT_FILE3 = join14(DEFAULT_CONFIG_DIR2, "github-emails.yaml");
65705
65812
  var NOREPLY_REGEX = /^\d+\+[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})@users\.noreply\.github\.com$/;
65706
65813
  var STORE_VERSION3 = 1;
@@ -65960,7 +66067,7 @@ async function changeDirectory(session, newDir, username, ctx) {
65960
66067
  return;
65961
66068
  }
65962
66069
  const expandedDir = newDir.startsWith("~") ? newDir.replace("~", process.env.HOME || "") : newDir;
65963
- const absoluteDir = resolve6(expandedDir);
66070
+ const absoluteDir = resolve7(expandedDir);
65964
66071
  const formatter = session.platform.getFormatter();
65965
66072
  if (!existsSync12(absoluteDir)) {
65966
66073
  await postError(session, `Directory does not exist: ${formatter.formatCode(newDir)}`);
@@ -66451,7 +66558,7 @@ async function reportBug(session, description, username, ctx, errorContext, atta
66451
66558
  });
66452
66559
  sessionLog8(session).info(`\uD83D\uDC1B Bug report preview created by @${username}: ${title}`);
66453
66560
  }
66454
- async function handleBugReportApproval(session, isApproved, username, ctx) {
66561
+ async function handleBugReportApproval(session, isApproved, username, ctx, exec) {
66455
66562
  const pending = session.messageManager?.getPendingBugReport();
66456
66563
  if (!pending)
66457
66564
  return;
@@ -66461,7 +66568,7 @@ async function handleBugReportApproval(session, isApproved, username, ctx) {
66461
66568
  const formatter = session.platform.getFormatter();
66462
66569
  if (isApproved) {
66463
66570
  try {
66464
- const issueUrl = await createGitHubIssue(pending.title, pending.body, session.workingDir);
66571
+ const issueUrl = await createGitHubIssue(pending.title, pending.body, session.workingDir, exec);
66465
66572
  await updatePostSuccess(session, pending.postId, `${formatter.formatBold("Bug report submitted")}: ${issueUrl}`);
66466
66573
  sessionLog8(session).info(`\uD83D\uDC1B Bug report created by @${username}: ${issueUrl}`);
66467
66574
  } catch (err) {
@@ -67335,6 +67442,7 @@ function createMessageManager(session, ctx) {
67335
67442
  sessionId: session.sessionId,
67336
67443
  worktreePath: session.worktreeInfo?.worktreePath,
67337
67444
  worktreeBranch: session.worktreeInfo?.branch,
67445
+ turnMarker: ctx.ops.getPlatformOverhead(session.platformId).turnMarker,
67338
67446
  registerPost: (postId, options) => {
67339
67447
  ctx.ops.registerPost(postId, session.threadId);
67340
67448
  postTracker.register(postId, session.threadId, session.sessionId, options);
@@ -67977,7 +68085,7 @@ ${sessionFormatter.formatItalic(outcome)}`;
67977
68085
  await withErrorHandling(() => session.platform.updatePost(postId, resumeMsg), { action: "Update timeout/shutdown post for resume", session });
67978
68086
  session.lifecyclePostId = undefined;
67979
68087
  transitionTo(session, "active");
67980
- } else {
68088
+ } else if (shouldPostLifecycle(ctx.ops.getPlatformOverhead(session.platformId).lifecycle, "resumed")) {
67981
68089
  const suffix = trigger === "platform-enabled" ? " (platform re-enabled)" : ` after bot restart (v${VERSION})`;
67982
68090
  const restartMsg = `${sessionFormatter.formatBold("Session resumed")}${suffix}
67983
68091
  ${sessionFormatter.formatItalic(outcome)}`;
@@ -68114,7 +68222,7 @@ async function handleExit(sessionId, code, ctx, source) {
68114
68222
  cleanupSessionTimers(session);
68115
68223
  await closeThreadLogger(session, "interrupt", { exitCode: code }, "pause");
68116
68224
  const message = session.lifecycle.hasClaudeResponded ? `ℹ️ Session paused. Send a new message to continue.` : `ℹ️ Session ended before Claude could respond. Send a new message to start fresh.`;
68117
- const pausePost = await withErrorHandling(() => post(session, "info", message), { action: "Post session pause notification", session });
68225
+ const pausePost = shouldPostLifecycle(ctx.ops.getPlatformOverhead(session.platformId).lifecycle, "paused") ? await withErrorHandling(() => post(session, "info", message), { action: "Post session pause notification", session }) : null;
68118
68226
  if (session.lifecycle.hasClaudeResponded) {
68119
68227
  transitionTo(session, "paused");
68120
68228
  if (pausePost) {
@@ -68193,7 +68301,7 @@ Please start a new session.`), { action: "Post session permanent failure", sessi
68193
68301
  await session.platform.unpinPost(exitTaskState.postId).catch(() => {});
68194
68302
  }
68195
68303
  await ctx.ops.flush(session);
68196
- if (code !== 0 && code !== null) {
68304
+ if (code !== 0 && code !== null && shouldPostLifecycle(ctx.ops.getPlatformOverhead(session.platformId).lifecycle, "abnormal-exit")) {
68197
68305
  const exitFormatter = session.platform.getFormatter();
68198
68306
  await post(session, "info", exitFormatter.formatBold(`[Exited: ${code}]`));
68199
68307
  }
@@ -68267,7 +68375,7 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
68267
68375
  const postId = session.lifecyclePostId;
68268
68376
  await withErrorHandling(() => session.platform.updatePost(postId, `⏱️ ${timeoutMessage}`), { action: "Update timeout post", session });
68269
68377
  } else {
68270
- const timeoutPost = await withErrorHandling(() => post(session, "timeout", timeoutMessage), { action: "Post session timeout", session });
68378
+ const timeoutPost = shouldPostLifecycle(ctx.ops.getPlatformOverhead(session.platformId).lifecycle, "timed-out") ? await withErrorHandling(() => post(session, "timeout", timeoutMessage), { action: "Post session timeout", session }) : null;
68271
68379
  if (timeoutPost) {
68272
68380
  session.lifecyclePostId = timeoutPost.id;
68273
68381
  ctx.ops.registerPost(timeoutPost.id, session.threadId);
@@ -68280,7 +68388,8 @@ async function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
68280
68388
  continue;
68281
68389
  }
68282
68390
  const warningThresholdMs = timeoutMs - warningMs;
68283
- if (idleMs > warningThresholdMs && !session.timeoutWarningPosted) {
68391
+ const idleWarningWanted = shouldPostLifecycle(ctx.ops.getPlatformOverhead(session.platformId).lifecycle, "idle-warning");
68392
+ if (idleWarningWanted && idleMs > warningThresholdMs && !session.timeoutWarningPosted) {
68284
68393
  const remainingMins = Math.max(0, Math.round((timeoutMs - idleMs) / 60000));
68285
68394
  const warningFormatter = session.platform.getFormatter();
68286
68395
  const warnWaiting = session.isProcessing && (session.messageManager?.hasPendingApproval() === true || session.messageManager?.hasPendingQuestions() === true);
@@ -70416,7 +70525,7 @@ class MattermostClient extends BasePlatformClient {
70416
70525
  const post = await this.api("POST", "/posts", request);
70417
70526
  return this.normalizePlatformPost(post);
70418
70527
  }
70419
- async updatePost(postId, message) {
70528
+ async updatePost(postId, message, _options) {
70420
70529
  const request = {
70421
70530
  id: postId,
70422
70531
  message
@@ -71464,6 +71573,9 @@ class SlackClient extends BasePlatformClient {
71464
71573
  if (resolvedThreadId) {
71465
71574
  body.thread_ts = resolvedThreadId;
71466
71575
  }
71576
+ if (options?.metadata) {
71577
+ body.metadata = options.metadata;
71578
+ }
71467
71579
  const response = await this.api("POST", "chat.postMessage", body);
71468
71580
  return {
71469
71581
  id: response.ts,
@@ -71475,13 +71587,16 @@ class SlackClient extends BasePlatformClient {
71475
71587
  createAt: Math.floor(parseFloat(response.ts) * 1000)
71476
71588
  };
71477
71589
  }
71478
- async updatePost(postId, message) {
71590
+ async updatePost(postId, message, options) {
71479
71591
  const truncatedMessage = this.truncateMessageIfNeeded(message);
71480
- const response = await this.api("POST", "chat.update", {
71592
+ const body = {
71481
71593
  channel: this.channelId,
71482
71594
  ts: postId,
71483
71595
  text: truncatedMessage
71484
- });
71596
+ };
71597
+ if (options?.metadata)
71598
+ body.metadata = options.metadata;
71599
+ const response = await this.api("POST", "chat.update", body);
71485
71600
  return {
71486
71601
  id: response.ts,
71487
71602
  platformId: this.platformId,
@@ -72973,7 +73088,9 @@ class SessionManager extends EventEmitter4 {
72973
73088
  this.platforms.set(platformId, client);
72974
73089
  this.platformOverhead.set(platformId, {
72975
73090
  sessionHeader: options?.overhead?.sessionHeader ?? DEFAULT_OVERHEAD_VISIBILITY,
72976
- stickyMessage: options?.overhead?.stickyMessage ?? DEFAULT_OVERHEAD_VISIBILITY
73091
+ stickyMessage: options?.overhead?.stickyMessage ?? DEFAULT_OVERHEAD_VISIBILITY,
73092
+ lifecycle: options?.overhead?.lifecycle ?? DEFAULT_OVERHEAD_VISIBILITY,
73093
+ turnMarker: options?.overhead?.turnMarker ?? DEFAULT_TURN_MARKER
72977
73094
  });
72978
73095
  this.platformMemory.set(platformId, options?.memory ?? DEFAULT_MEMORY_CONFIG);
72979
73096
  this.platformRoutines.set(platformId, options?.routinesEnabled ?? true);
@@ -73111,7 +73228,9 @@ class SessionManager extends EventEmitter4 {
73111
73228
  getClaudeAccountPoolStatus: () => this.accountPool.status(),
73112
73229
  getPlatformOverhead: (pid) => this.platformOverhead.get(pid) ?? {
73113
73230
  sessionHeader: DEFAULT_OVERHEAD_VISIBILITY,
73114
- stickyMessage: DEFAULT_OVERHEAD_VISIBILITY
73231
+ stickyMessage: DEFAULT_OVERHEAD_VISIBILITY,
73232
+ lifecycle: DEFAULT_OVERHEAD_VISIBILITY,
73233
+ turnMarker: DEFAULT_TURN_MARKER
73115
73234
  },
73116
73235
  getPlatformMemoryConfig: (pid) => this.platformMemory.get(pid) ?? DEFAULT_MEMORY_CONFIG,
73117
73236
  isRoutinesEnabled: (pid) => this.platformRoutines.get(pid) ?? true,
@@ -73404,7 +73523,7 @@ class SessionManager extends EventEmitter4 {
73404
73523
  const pauseMessage = `⏸️ ${fmt.formatBold("Platform disabled")} - session paused. Re-enable platform to resume.`;
73405
73524
  if (session.lifecyclePostId) {
73406
73525
  await session.platform.updatePost(session.lifecyclePostId, pauseMessage);
73407
- } else {
73526
+ } else if (shouldPostLifecycle(this.platformOverhead.get(session.platformId)?.lifecycle ?? DEFAULT_OVERHEAD_VISIBILITY, "paused")) {
73408
73527
  const post = await session.platform.createPost(pauseMessage, session.threadId);
73409
73528
  session.lifecyclePostId = post.id;
73410
73529
  }
@@ -73896,7 +74015,7 @@ Mention me to start a session in this worktree.`, threadId);
73896
74015
  const shutdownMessage = `⏸️ ${fmt.formatBold("Bot shutting down")} - session will resume on restart`;
73897
74016
  if (session.lifecyclePostId) {
73898
74017
  await session.platform.updatePost(session.lifecyclePostId, shutdownMessage);
73899
- } else {
74018
+ } else if (shouldPostLifecycle(this.platformOverhead.get(session.platformId)?.lifecycle ?? DEFAULT_OVERHEAD_VISIBILITY, "shutdown")) {
73900
74019
  const post = await session.platform.createPost(shutdownMessage, session.threadId);
73901
74020
  session.lifecyclePostId = post.id;
73902
74021
  }
@@ -86492,10 +86611,11 @@ class UpdateScheduler extends EventEmitter8 {
86492
86611
  }
86493
86612
 
86494
86613
  // src/auto-update/installer.ts
86614
+ init_state_home();
86495
86615
  import { spawn as spawn4, spawnSync } from "child_process";
86496
86616
  import { existsSync as existsSync17, readFileSync as readFileSync12, mkdirSync as mkdirSync8 } from "fs";
86497
- import { dirname as dirname9, resolve as resolve7 } from "path";
86498
- import { homedir as homedir10 } from "os";
86617
+ import { dirname as dirname9, resolve as resolve8 } from "path";
86618
+ import { homedir as homedir3 } from "os";
86499
86619
  init_logger();
86500
86620
  var log56 = createLogger("installer");
86501
86621
  function detectPackageManager() {
@@ -86536,7 +86656,7 @@ function normalizePath(p) {
86536
86656
  function detectOriginalInstaller() {
86537
86657
  try {
86538
86658
  const scriptPath = normalizePath(process.argv[1] || "");
86539
- const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve7(homedir10(), ".bun"));
86659
+ const bunGlobalDir = normalizePath(process.env.BUN_INSTALL || resolve8(homedir3(), ".bun"));
86540
86660
  if (scriptPath.startsWith(bunGlobalDir)) {
86541
86661
  return "bun";
86542
86662
  }
@@ -86556,7 +86676,7 @@ function detectOriginalInstaller() {
86556
86676
  return null;
86557
86677
  }
86558
86678
  }
86559
- var STATE_PATH = resolve7(homedir10(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
86679
+ var STATE_PATH = resolve8(stateHome(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
86560
86680
  var PACKAGE_NAME2 = "claude-threads";
86561
86681
  function loadUpdateState() {
86562
86682
  try {
@@ -86978,6 +87098,102 @@ ${getRollbackInstructions(VERSION)}`).catch(() => {});
86978
87098
  this.callbacks.refreshUI().catch(() => {});
86979
87099
  }
86980
87100
  }
87101
+ // src/utils/instance-lock.ts
87102
+ init_state_home();
87103
+ init_logger();
87104
+ import { closeSync as closeSync2, fstatSync, linkSync, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync13, renameSync as renameSync2, statSync as statSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "fs";
87105
+ import { dirname as dirname10, join as join19 } from "path";
87106
+ var log59 = createLogger("lock");
87107
+ var LOCKED_EXIT_CODE = 3;
87108
+ function acquireInstanceLock() {
87109
+ const path = join19(stateHome(), ".config", "claude-threads", "instance.lock");
87110
+ mkdirSync9(dirname10(path), { recursive: true, mode: 448 });
87111
+ const tmp = `${path}.${process.pid}`;
87112
+ writeFileSync6(tmp, String(process.pid), { mode: 384 });
87113
+ try {
87114
+ for (let attempt = 0;attempt < 5; attempt++) {
87115
+ let linked;
87116
+ try {
87117
+ linked = tryLink(tmp, path);
87118
+ } catch (err) {
87119
+ log59.warn(`instance lock unavailable on this filesystem (${err.code ?? String(err)}); running without it`);
87120
+ return () => {};
87121
+ }
87122
+ if (linked)
87123
+ return () => release(path);
87124
+ const seen = inspect(path);
87125
+ if (!seen)
87126
+ continue;
87127
+ if (seen.pid === process.pid)
87128
+ return () => release(path);
87129
+ if (seen.pid && isAlive(seen.pid)) {
87130
+ throw new Error(`another claude-threads instance (pid ${seen.pid}) already uses ${dirname10(path)} — ` + `set CLAUDE_THREADS_HOME to run a second bot; if pid ${seen.pid} is not a claude-threads process, delete ${path}`);
87131
+ }
87132
+ const aside = `${path}.stale.${process.pid}`;
87133
+ if (!tryRename(path, aside))
87134
+ continue;
87135
+ if (statSync6(aside).ino !== seen.ino) {
87136
+ tryLink(aside, path);
87137
+ unlinkSync4(aside);
87138
+ continue;
87139
+ }
87140
+ unlinkSync4(aside);
87141
+ }
87142
+ throw new Error(`could not acquire ${path}: lost the race five times`);
87143
+ } finally {
87144
+ try {
87145
+ unlinkSync4(tmp);
87146
+ } catch {}
87147
+ }
87148
+ }
87149
+ function inspect(path) {
87150
+ let fd;
87151
+ try {
87152
+ fd = openSync2(path, "r");
87153
+ } catch {
87154
+ return;
87155
+ }
87156
+ try {
87157
+ return { pid: Number(readFileSync13(fd, "utf8").trim()) || undefined, ino: fstatSync(fd).ino };
87158
+ } finally {
87159
+ closeSync2(fd);
87160
+ }
87161
+ }
87162
+ function tryLink(src, path) {
87163
+ try {
87164
+ linkSync(src, path);
87165
+ return true;
87166
+ } catch (err) {
87167
+ if (err.code === "EEXIST")
87168
+ return false;
87169
+ throw err;
87170
+ }
87171
+ }
87172
+ function tryRename(from, to) {
87173
+ try {
87174
+ renameSync2(from, to);
87175
+ return true;
87176
+ } catch (err) {
87177
+ if (err.code === "ENOENT")
87178
+ return false;
87179
+ throw err;
87180
+ }
87181
+ }
87182
+ function release(path) {
87183
+ try {
87184
+ if (inspect(path)?.pid === process.pid)
87185
+ unlinkSync4(path);
87186
+ } catch {}
87187
+ }
87188
+ function isAlive(pid) {
87189
+ try {
87190
+ process.kill(pid, 0);
87191
+ return true;
87192
+ } catch (err) {
87193
+ return err.code === "EPERM";
87194
+ }
87195
+ }
87196
+
86981
87197
  // src/index.ts
86982
87198
  function createPlatformClient(config) {
86983
87199
  switch (config.type) {
@@ -87207,6 +87423,15 @@ async function startWithoutDaemon() {
87207
87423
  console.error(yellow(` ⚠️ --skip-version-check: ${prefix}${claudeValidation.message}`));
87208
87424
  console.error("");
87209
87425
  }
87426
+ let releaseInstanceLock;
87427
+ try {
87428
+ releaseInstanceLock = acquireInstanceLock();
87429
+ } catch (err) {
87430
+ console.error(red(` ❌ ${err instanceof Error ? err.message : String(err)}`));
87431
+ console.error("");
87432
+ process.exit(LOCKED_EXIT_CODE);
87433
+ }
87434
+ process.on("exit", () => releaseInstanceLock());
87210
87435
  if (process.env.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB === "1") {
87211
87436
  const hasSkipPermissionPlatform = config.platforms.some((p) => p.skipPermissions === true);
87212
87437
  if (hasSkipPermissionPlatform) {
@@ -87385,7 +87610,9 @@ async function startWithoutDaemon() {
87385
87610
  session.addPlatform(platformConfig.id, client, {
87386
87611
  overhead: {
87387
87612
  sessionHeader: resolveOverheadVisibility(platformConfig.sessionHeader, `platforms[${platformConfig.id}].sessionHeader`),
87388
- stickyMessage: resolveOverheadVisibility(platformConfig.stickyMessage, `platforms[${platformConfig.id}].stickyMessage`)
87613
+ stickyMessage: resolveOverheadVisibility(platformConfig.stickyMessage, `platforms[${platformConfig.id}].stickyMessage`),
87614
+ lifecycle: resolveOverheadVisibility(platformConfig.lifecycle, `platforms[${platformConfig.id}].lifecycle`),
87615
+ turnMarker: resolveTurnMarker(platformConfig.turnMarker, platformConfig.turnMarkerEmoji, platformConfig.type, `platforms[${platformConfig.id}]`)
87389
87616
  },
87390
87617
  memory: resolveMemoryConfig(platformConfig.memory, `platforms[${platformConfig.id}].memory`),
87391
87618
  routinesEnabled: resolveRoutinesEnabled(platformConfig.routines, `platforms[${platformConfig.id}].routines`),
@@ -87412,7 +87639,9 @@ async function startWithoutDaemon() {
87412
87639
  session.addPlatform(dmConfig.id, dmClient, {
87413
87640
  overhead: {
87414
87641
  sessionHeader: resolveOverheadVisibility(dmConfig.sessionHeader, `dm[${dmConfig.id}].sessionHeader`),
87415
- stickyMessage: "hidden"
87642
+ stickyMessage: "hidden",
87643
+ lifecycle: resolveOverheadVisibility(dmConfig.lifecycle, `dm[${dmConfig.id}].lifecycle`),
87644
+ turnMarker: resolveTurnMarker(dmConfig.turnMarker, dmConfig.turnMarkerEmoji, dmConfig.type, `dm[${dmConfig.id}]`)
87416
87645
  },
87417
87646
  memory: resolveMemoryConfig(dmConfig.memory, `dm[${dmConfig.id}].memory`),
87418
87647
  routinesEnabled: resolveRoutinesEnabled(dmConfig.routines, `dm[${dmConfig.id}].routines`),