@integrity-labs/agt-cli 0.26.2-eng5706.0 → 0.26.2

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.
@@ -14299,10 +14299,82 @@ function isMode(value) {
14299
14299
  return value === "thinking" || value === "working" || value === "waiting";
14300
14300
  }
14301
14301
 
14302
+ // src/mcp-spawn-lock.ts
14303
+ import {
14304
+ existsSync,
14305
+ mkdirSync,
14306
+ readFileSync,
14307
+ renameSync,
14308
+ unlinkSync,
14309
+ writeFileSync
14310
+ } from "fs";
14311
+ import { join } from "path";
14312
+ function defaultIsPidAlive(pid) {
14313
+ if (!Number.isFinite(pid) || pid <= 0) return false;
14314
+ try {
14315
+ process.kill(pid, 0);
14316
+ return true;
14317
+ } catch (err) {
14318
+ const code = err.code;
14319
+ if (code === "ESRCH") return false;
14320
+ return true;
14321
+ }
14322
+ }
14323
+ function acquireMcpSpawnLock(args) {
14324
+ const { agentDir, basename, options = {} } = args;
14325
+ if (!agentDir) return { kind: "no-agent-dir" };
14326
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
14327
+ const selfPid = options.selfPid ?? process.pid;
14328
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
14329
+ const path = join(agentDir, basename);
14330
+ const existing = readLockHolder(path);
14331
+ if (existing) {
14332
+ if (existing.pid === selfPid) {
14333
+ return { kind: "acquired", path };
14334
+ }
14335
+ if (isPidAlive(existing.pid)) {
14336
+ return { kind: "blocked", path, holder: existing };
14337
+ }
14338
+ }
14339
+ mkdirSync(agentDir, { recursive: true, mode: 448 });
14340
+ const tmpPath = `${path}.${selfPid}.tmp`;
14341
+ const payload = { pid: selfPid, started_at: now() };
14342
+ writeFileSync(tmpPath, JSON.stringify(payload), { mode: 384 });
14343
+ renameSync(tmpPath, path);
14344
+ return { kind: "acquired", path };
14345
+ }
14346
+ function releaseMcpSpawnLock(lockPath, opts = {}) {
14347
+ if (!lockPath) return;
14348
+ const selfPid = opts.selfPid ?? process.pid;
14349
+ const existing = readLockHolder(lockPath);
14350
+ if (!existing) return;
14351
+ if (existing.pid !== selfPid) return;
14352
+ try {
14353
+ unlinkSync(lockPath);
14354
+ } catch {
14355
+ }
14356
+ }
14357
+ function readLockHolder(path) {
14358
+ if (!existsSync(path)) return null;
14359
+ try {
14360
+ const raw = readFileSync(path, "utf8");
14361
+ const parsed = JSON.parse(raw);
14362
+ const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
14363
+ if (!Number.isFinite(pid) || pid <= 0) return null;
14364
+ const startedAt = typeof parsed.started_at === "string" ? parsed.started_at : "";
14365
+ return { pid, started_at: startedAt };
14366
+ } catch {
14367
+ return null;
14368
+ }
14369
+ }
14370
+
14302
14371
  // src/direct-chat-channel.ts
14372
+ import { homedir } from "os";
14373
+ import { join as join2 } from "path";
14303
14374
  var AGT_HOST = process.env.AGT_HOST;
14304
14375
  var AGT_API_KEY = process.env.AGT_API_KEY;
14305
14376
  var AGT_AGENT_ID = process.env.AGT_AGENT_ID;
14377
+ var DIRECT_CHAT_AGENT_DIR = AGT_AGENT_ID ? join2(homedir(), ".augmented", AGT_AGENT_ID) : null;
14306
14378
  if (!AGT_HOST || !AGT_API_KEY || !AGT_AGENT_ID) {
14307
14379
  process.stderr.write(
14308
14380
  "direct-chat-channel: Missing AGT_HOST, AGT_API_KEY, or AGT_AGENT_ID. Cannot start.\n"
@@ -14488,6 +14560,22 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
14488
14560
  throw new Error(`Unknown tool: ${name}`);
14489
14561
  });
14490
14562
  await mcp.connect(new StdioServerTransport());
14563
+ var acquiredLockPath = null;
14564
+ {
14565
+ const lockResult = acquireMcpSpawnLock({
14566
+ agentDir: DIRECT_CHAT_AGENT_DIR,
14567
+ basename: "direct-chat-channel.lock"
14568
+ });
14569
+ if (lockResult.kind === "blocked") {
14570
+ process.stderr.write(
14571
+ `direct-chat-channel: another instance pid=${lockResult.holder.pid} already running (since ${lockResult.holder.started_at}), exiting
14572
+ `
14573
+ );
14574
+ process.exit(0);
14575
+ } else if (lockResult.kind === "acquired") {
14576
+ acquiredLockPath = lockResult.path;
14577
+ }
14578
+ }
14491
14579
  process.stderr.write(
14492
14580
  `direct-chat-channel: Started (agent=${AGT_AGENT_ID}, polling=disabled \u2014 using Realtime)
14493
14581
  `
@@ -14498,6 +14586,10 @@ function shutdown(reason) {
14498
14586
  isShuttingDown = true;
14499
14587
  process.stderr.write(`direct-chat-channel: ${reason} \u2014 exiting
14500
14588
  `);
14589
+ try {
14590
+ releaseMcpSpawnLock(acquiredLockPath);
14591
+ } catch {
14592
+ }
14501
14593
  setTimeout(() => process.exit(0), 100).unref();
14502
14594
  }
14503
14595
  process.stdin.on("close", () => shutdown("stdin closed"));
@@ -14891,17 +14891,17 @@ var StdioServerTransport = class {
14891
14891
  import {
14892
14892
  chmodSync,
14893
14893
  createWriteStream,
14894
- existsSync,
14895
- mkdirSync as mkdirSync2,
14896
- readFileSync as readFileSync2,
14894
+ existsSync as existsSync2,
14895
+ mkdirSync as mkdirSync3,
14896
+ readFileSync as readFileSync3,
14897
14897
  readdirSync,
14898
- renameSync,
14898
+ renameSync as renameSync2,
14899
14899
  statSync,
14900
- unlinkSync,
14900
+ unlinkSync as unlinkSync2,
14901
14901
  watch,
14902
- writeFileSync as writeFileSync2
14902
+ writeFileSync as writeFileSync3
14903
14903
  } from "fs";
14904
- import { basename, join as join2, resolve as resolve2 } from "path";
14904
+ import { basename, join as join3, resolve as resolve2 } from "path";
14905
14905
  import { homedir as homedir2 } from "os";
14906
14906
  import { createHash, randomUUID as randomUUID2 } from "crypto";
14907
14907
 
@@ -15607,6 +15607,75 @@ function createSlackBotUserIdClient(args) {
15607
15607
  };
15608
15608
  }
15609
15609
 
15610
+ // src/mcp-spawn-lock.ts
15611
+ import {
15612
+ existsSync,
15613
+ mkdirSync as mkdirSync2,
15614
+ readFileSync as readFileSync2,
15615
+ renameSync,
15616
+ unlinkSync,
15617
+ writeFileSync as writeFileSync2
15618
+ } from "fs";
15619
+ import { join as join2 } from "path";
15620
+ function defaultIsPidAlive(pid) {
15621
+ if (!Number.isFinite(pid) || pid <= 0) return false;
15622
+ try {
15623
+ process.kill(pid, 0);
15624
+ return true;
15625
+ } catch (err) {
15626
+ const code = err.code;
15627
+ if (code === "ESRCH") return false;
15628
+ return true;
15629
+ }
15630
+ }
15631
+ function acquireMcpSpawnLock(args) {
15632
+ const { agentDir, basename: basename2, options = {} } = args;
15633
+ if (!agentDir) return { kind: "no-agent-dir" };
15634
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
15635
+ const selfPid = options.selfPid ?? process.pid;
15636
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
15637
+ const path = join2(agentDir, basename2);
15638
+ const existing = readLockHolder(path);
15639
+ if (existing) {
15640
+ if (existing.pid === selfPid) {
15641
+ return { kind: "acquired", path };
15642
+ }
15643
+ if (isPidAlive(existing.pid)) {
15644
+ return { kind: "blocked", path, holder: existing };
15645
+ }
15646
+ }
15647
+ mkdirSync2(agentDir, { recursive: true, mode: 448 });
15648
+ const tmpPath = `${path}.${selfPid}.tmp`;
15649
+ const payload = { pid: selfPid, started_at: now() };
15650
+ writeFileSync2(tmpPath, JSON.stringify(payload), { mode: 384 });
15651
+ renameSync(tmpPath, path);
15652
+ return { kind: "acquired", path };
15653
+ }
15654
+ function releaseMcpSpawnLock(lockPath, opts = {}) {
15655
+ if (!lockPath) return;
15656
+ const selfPid = opts.selfPid ?? process.pid;
15657
+ const existing = readLockHolder(lockPath);
15658
+ if (!existing) return;
15659
+ if (existing.pid !== selfPid) return;
15660
+ try {
15661
+ unlinkSync(lockPath);
15662
+ } catch {
15663
+ }
15664
+ }
15665
+ function readLockHolder(path) {
15666
+ if (!existsSync(path)) return null;
15667
+ try {
15668
+ const raw = readFileSync2(path, "utf8");
15669
+ const parsed = JSON.parse(raw);
15670
+ const pid = typeof parsed.pid === "number" ? parsed.pid : Number(parsed.pid);
15671
+ if (!Number.isFinite(pid) || pid <= 0) return null;
15672
+ const startedAt = typeof parsed.started_at === "string" ? parsed.started_at : "";
15673
+ return { pid, started_at: startedAt };
15674
+ } catch {
15675
+ return null;
15676
+ }
15677
+ }
15678
+
15610
15679
  // src/slack-channel.ts
15611
15680
  var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
15612
15681
  var APP_TOKEN = process.env.SLACK_APP_TOKEN;
@@ -15658,9 +15727,9 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
15658
15727
  peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
15659
15728
  peer_disabled_mode: SLACK_PEER_DISABLED_MODE
15660
15729
  };
15661
- var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join2(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15662
- var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15663
- var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15730
+ var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join3(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
15731
+ var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
15732
+ var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join3(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
15664
15733
  var SLACK_MAX_RECOVERY_ATTEMPTS = 3;
15665
15734
  function redactSlackId(id) {
15666
15735
  if (!id) return "<none>";
@@ -15672,7 +15741,7 @@ function safeSlackMarkerName(channel, threadTs, messageTs) {
15672
15741
  }
15673
15742
  function slackPendingInboundPath(channel, threadTs, messageTs) {
15674
15743
  if (!SLACK_PENDING_INBOUND_DIR) return null;
15675
- return join2(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15744
+ return join3(SLACK_PENDING_INBOUND_DIR, safeSlackMarkerName(channel, threadTs, messageTs));
15676
15745
  }
15677
15746
  function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
15678
15747
  const path = slackPendingInboundPath(channel, threadTs, messageTs);
@@ -15684,8 +15753,8 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
15684
15753
  received_at: (/* @__PURE__ */ new Date()).toISOString()
15685
15754
  };
15686
15755
  try {
15687
- mkdirSync2(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
15688
- writeFileSync2(path, JSON.stringify(marker), { mode: 384 });
15756
+ mkdirSync3(SLACK_PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
15757
+ writeFileSync3(path, JSON.stringify(marker), { mode: 384 });
15689
15758
  } catch (err) {
15690
15759
  process.stderr.write(
15691
15760
  `slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
@@ -15702,7 +15771,7 @@ function clearAllSlackPendingMarkersForThread(channel, threadTs) {
15702
15771
  for (const f of readdirSync(SLACK_PENDING_INBOUND_DIR)) {
15703
15772
  if (!f.startsWith(prefix) || !f.endsWith(".json")) continue;
15704
15773
  try {
15705
- unlinkSync(join2(SLACK_PENDING_INBOUND_DIR, f));
15774
+ unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, f));
15706
15775
  } catch {
15707
15776
  }
15708
15777
  }
@@ -15723,17 +15792,17 @@ function slackNextRetryName(filename) {
15723
15792
  async function processSlackRecoveryOutboxFile(filename) {
15724
15793
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
15725
15794
  if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
15726
- const fullPath = join2(SLACK_RECOVERY_OUTBOX_DIR, filename);
15795
+ const fullPath = join3(SLACK_RECOVERY_OUTBOX_DIR, filename);
15727
15796
  let payload;
15728
15797
  try {
15729
- payload = JSON.parse(readFileSync2(fullPath, "utf-8"));
15798
+ payload = JSON.parse(readFileSync3(fullPath, "utf-8"));
15730
15799
  } catch (err) {
15731
15800
  process.stderr.write(
15732
15801
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
15733
15802
  `
15734
15803
  );
15735
15804
  try {
15736
- renameSync(fullPath, `${fullPath}.parse-error.poison`);
15805
+ renameSync2(fullPath, `${fullPath}.parse-error.poison`);
15737
15806
  } catch {
15738
15807
  }
15739
15808
  return;
@@ -15744,7 +15813,7 @@ async function processSlackRecoveryOutboxFile(filename) {
15744
15813
  `
15745
15814
  );
15746
15815
  try {
15747
- renameSync(fullPath, `${fullPath}.malformed.poison`);
15816
+ renameSync2(fullPath, `${fullPath}.malformed.poison`);
15748
15817
  } catch {
15749
15818
  }
15750
15819
  return;
@@ -15792,7 +15861,7 @@ async function processSlackRecoveryOutboxFile(filename) {
15792
15861
  }
15793
15862
  if (sendSucceeded) {
15794
15863
  try {
15795
- unlinkSync(fullPath);
15864
+ unlinkSync2(fullPath);
15796
15865
  } catch {
15797
15866
  }
15798
15867
  return;
@@ -15800,7 +15869,7 @@ async function processSlackRecoveryOutboxFile(filename) {
15800
15869
  const next = slackNextRetryName(filename);
15801
15870
  if (next) {
15802
15871
  try {
15803
- renameSync(fullPath, join2(SLACK_RECOVERY_OUTBOX_DIR, next.next));
15872
+ renameSync2(fullPath, join3(SLACK_RECOVERY_OUTBOX_DIR, next.next));
15804
15873
  if (next.attempt >= SLACK_MAX_RECOVERY_ATTEMPTS) {
15805
15874
  process.stderr.write(
15806
15875
  `slack-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
@@ -15839,7 +15908,7 @@ function scanSlackRecoveryRetries() {
15839
15908
  if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
15840
15909
  let mtimeMs;
15841
15910
  try {
15842
- mtimeMs = statSync(join2(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
15911
+ mtimeMs = statSync(join3(SLACK_RECOVERY_OUTBOX_DIR, f)).mtimeMs;
15843
15912
  } catch {
15844
15913
  continue;
15845
15914
  }
@@ -15851,7 +15920,7 @@ function scanSlackRecoveryRetries() {
15851
15920
  function startSlackRecoveryOutboxWatcher() {
15852
15921
  if (!SLACK_RECOVERY_OUTBOX_DIR) return;
15853
15922
  try {
15854
- mkdirSync2(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
15923
+ mkdirSync3(SLACK_RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
15855
15924
  } catch (err) {
15856
15925
  process.stderr.write(
15857
15926
  `slack-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
@@ -15869,7 +15938,7 @@ function startSlackRecoveryOutboxWatcher() {
15869
15938
  const watcher = watch(SLACK_RECOVERY_OUTBOX_DIR, (event, filename) => {
15870
15939
  if (event !== "rename" || !filename) return;
15871
15940
  if (!isFirstAttemptSlackOutboxFile(filename)) return;
15872
- if (existsSync(join2(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
15941
+ if (existsSync2(join3(SLACK_RECOVERY_OUTBOX_DIR, filename))) {
15873
15942
  void processSlackRecoveryOutboxFile(filename);
15874
15943
  }
15875
15944
  });
@@ -15890,7 +15959,7 @@ function trackPendingMessage(channel, threadTs, messageTs) {
15890
15959
  }
15891
15960
  function sweepSlackStaleMarkersOnBoot() {
15892
15961
  if (!SLACK_PENDING_INBOUND_DIR) return;
15893
- if (!existsSync(SLACK_PENDING_INBOUND_DIR)) return;
15962
+ if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
15894
15963
  let filenames;
15895
15964
  try {
15896
15965
  filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
@@ -15906,17 +15975,17 @@ function sweepSlackStaleMarkersOnBoot() {
15906
15975
  for (const filename of filenames) {
15907
15976
  if (!filename.endsWith(".json")) continue;
15908
15977
  if (filename.endsWith(".tmp")) continue;
15909
- const fullPath = join2(SLACK_PENDING_INBOUND_DIR, filename);
15978
+ const fullPath = join3(SLACK_PENDING_INBOUND_DIR, filename);
15910
15979
  let marker;
15911
15980
  try {
15912
- marker = JSON.parse(readFileSync2(fullPath, "utf-8"));
15981
+ marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
15913
15982
  } catch (err) {
15914
15983
  process.stderr.write(
15915
15984
  `slack-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactSlackId(filename)}: ${err.message}
15916
15985
  `
15917
15986
  );
15918
15987
  try {
15919
- unlinkSync(fullPath);
15988
+ unlinkSync2(fullPath);
15920
15989
  } catch {
15921
15990
  }
15922
15991
  cleared++;
@@ -15925,7 +15994,7 @@ function sweepSlackStaleMarkersOnBoot() {
15925
15994
  const { channel, thread_ts, message_ts, received_at } = marker;
15926
15995
  if (!channel || !thread_ts || !message_ts || !received_at) {
15927
15996
  try {
15928
- unlinkSync(fullPath);
15997
+ unlinkSync2(fullPath);
15929
15998
  } catch {
15930
15999
  }
15931
16000
  cleared++;
@@ -15934,7 +16003,7 @@ function sweepSlackStaleMarkersOnBoot() {
15934
16003
  const receivedAtMs = Date.parse(received_at);
15935
16004
  if (!Number.isFinite(receivedAtMs) || receivedAtMs > now || now - receivedAtMs > STALE_MARKER_MS) {
15936
16005
  try {
15937
- unlinkSync(fullPath);
16006
+ unlinkSync2(fullPath);
15938
16007
  } catch {
15939
16008
  }
15940
16009
  cleared++;
@@ -15959,7 +16028,7 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
15959
16028
  if (!channel || !messageTs) return;
15960
16029
  clearPendingMessage(channel, messageTs);
15961
16030
  if (!SLACK_PENDING_INBOUND_DIR) return;
15962
- if (!existsSync(SLACK_PENDING_INBOUND_DIR)) return;
16031
+ if (!existsSync2(SLACK_PENDING_INBOUND_DIR)) return;
15963
16032
  let filenames;
15964
16033
  try {
15965
16034
  filenames = readdirSync(SLACK_PENDING_INBOUND_DIR);
@@ -15974,12 +16043,12 @@ function noteThreadActivityByMessageTs(channel, messageTs) {
15974
16043
  if (!filename.startsWith(channelPrefix)) continue;
15975
16044
  if (!filename.endsWith(messageSuffix)) continue;
15976
16045
  try {
15977
- unlinkSync(join2(SLACK_PENDING_INBOUND_DIR, filename));
16046
+ unlinkSync2(join3(SLACK_PENDING_INBOUND_DIR, filename));
15978
16047
  } catch {
15979
16048
  }
15980
16049
  }
15981
16050
  }
15982
- var RESTART_FLAGS_DIR = join2(homedir2(), ".augmented", "restart-flags");
16051
+ var RESTART_FLAGS_DIR = join3(homedir2(), ".augmented", "restart-flags");
15983
16052
  function hashChannelId(id) {
15984
16053
  return createHash("sha256").update(id).digest("hex").slice(0, 8);
15985
16054
  }
@@ -16146,10 +16215,10 @@ async function handleSlashCommandEnvelope(payload) {
16146
16215
  return;
16147
16216
  }
16148
16217
  try {
16149
- if (!existsSync(RESTART_FLAGS_DIR)) {
16150
- mkdirSync2(RESTART_FLAGS_DIR, { recursive: true });
16218
+ if (!existsSync2(RESTART_FLAGS_DIR)) {
16219
+ mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16151
16220
  }
16152
- const flagPath = join2(RESTART_FLAGS_DIR, `${codeName}.flag`);
16221
+ const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16153
16222
  const flag = {
16154
16223
  codeName,
16155
16224
  source: "slack",
@@ -16160,8 +16229,8 @@ async function handleSlashCommandEnvelope(payload) {
16160
16229
  }
16161
16230
  };
16162
16231
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
16163
- writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16164
- renameSync(tmpPath, flagPath);
16232
+ writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16233
+ renameSync2(tmpPath, flagPath);
16165
16234
  process.stderr.write(
16166
16235
  `slack-channel(${codeName}): /restart slash-command queued from channel ${hashChannelId(payload.channel_id)}
16167
16236
  `
@@ -16253,10 +16322,10 @@ async function handleHelpCommand(opts) {
16253
16322
  async function handleRestartCommand(opts) {
16254
16323
  const codeName = AGENT_CODE_NAME ?? "unknown";
16255
16324
  try {
16256
- if (!existsSync(RESTART_FLAGS_DIR)) {
16257
- mkdirSync2(RESTART_FLAGS_DIR, { recursive: true });
16325
+ if (!existsSync2(RESTART_FLAGS_DIR)) {
16326
+ mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
16258
16327
  }
16259
- const flagPath = join2(RESTART_FLAGS_DIR, `${codeName}.flag`);
16328
+ const flagPath = join3(RESTART_FLAGS_DIR, `${codeName}.flag`);
16260
16329
  const flag = {
16261
16330
  codeName,
16262
16331
  source: "slack",
@@ -16268,8 +16337,8 @@ async function handleRestartCommand(opts) {
16268
16337
  }
16269
16338
  };
16270
16339
  const tmpPath = `${flagPath}.${process.pid}.${randomUUID2()}.tmp`;
16271
- writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16272
- renameSync(tmpPath, flagPath);
16340
+ writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
16341
+ renameSync2(tmpPath, flagPath);
16273
16342
  process.stderr.write(
16274
16343
  `slack-channel(${codeName}): /restart queued from channel ${hashChannelId(opts.channel)}
16275
16344
  `
@@ -16315,7 +16384,7 @@ var THREAD_STORE_TTL_DAYS = parseTtlDays(process.env.SLACK_THREAD_FOLLOW_TTL_DAY
16315
16384
  var threadPersister = null;
16316
16385
  function resolveThreadStorePath() {
16317
16386
  if (!AGENT_CODE_NAME) return null;
16318
- return join2(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16387
+ return join3(homedir2(), ".augmented", AGENT_CODE_NAME, "slack-tracked-threads.json");
16319
16388
  }
16320
16389
  function parseTtlDays(raw) {
16321
16390
  if (!raw) return void 0;
@@ -16350,9 +16419,9 @@ if (!BOT_TOKEN || !APP_TOKEN) {
16350
16419
  var slackStderrLogStream = null;
16351
16420
  if (AGENT_CODE_NAME) {
16352
16421
  try {
16353
- const logDir = join2(homedir2(), ".augmented", AGENT_CODE_NAME);
16354
- mkdirSync2(logDir, { recursive: true });
16355
- slackStderrLogStream = createWriteStream(join2(logDir, "slack-channel-stderr.log"), {
16422
+ const logDir = join3(homedir2(), ".augmented", AGENT_CODE_NAME);
16423
+ mkdirSync3(logDir, { recursive: true });
16424
+ slackStderrLogStream = createWriteStream(join3(logDir, "slack-channel-stderr.log"), {
16356
16425
  flags: "a",
16357
16426
  mode: 384
16358
16427
  });
@@ -16965,7 +17034,7 @@ ${formatted}` : "Thread is empty or not found."
16965
17034
  };
16966
17035
  }
16967
17036
  size = stat.size;
16968
- bytes = readFileSync2(resolvedPath);
17037
+ bytes = readFileSync3(resolvedPath);
16969
17038
  } catch (err) {
16970
17039
  return {
16971
17040
  content: [{ type: "text", text: `Failed to read file: ${err.message}` }],
@@ -17683,8 +17752,8 @@ async function downloadSlackFile(fileId, codeName) {
17683
17752
  if (!isPathInside(savedPath, dir)) {
17684
17753
  throw new Error(`refusing to write ${savedPath} outside ${dir}`);
17685
17754
  }
17686
- mkdirSync2(dir, { recursive: true });
17687
- writeFileSync2(savedPath, bytes, { mode: 384 });
17755
+ mkdirSync3(dir, { recursive: true });
17756
+ writeFileSync3(savedPath, bytes, { mode: 384 });
17688
17757
  try {
17689
17758
  chmodSync(savedPath, 384);
17690
17759
  } catch {
@@ -17757,6 +17826,7 @@ async function resolveUserName(userId) {
17757
17826
  }
17758
17827
  var currentWs = null;
17759
17828
  var isShuttingDown = false;
17829
+ var acquiredLockPath = null;
17760
17830
  function connectSocketModeSafely() {
17761
17831
  void runOrRetry(connectSocketMode, {
17762
17832
  retryDelayMs: 1e4,
@@ -18062,6 +18132,10 @@ function shutdown(reason, exitCode = 0) {
18062
18132
  currentWs?.close();
18063
18133
  } catch {
18064
18134
  }
18135
+ try {
18136
+ releaseMcpSpawnLock(acquiredLockPath);
18137
+ } catch {
18138
+ }
18065
18139
  setTimeout(() => process.exit(exitCode), 500).unref();
18066
18140
  }
18067
18141
  process.stdin.on("close", () => shutdown("stdin closed"));
@@ -18074,6 +18148,21 @@ process.on("unhandledRejection", (reason) => {
18074
18148
  process.stderr.write(`slack-channel: unhandledRejection (continuing): ${msg}
18075
18149
  `);
18076
18150
  });
18151
+ {
18152
+ const lockResult = acquireMcpSpawnLock({
18153
+ agentDir: SLACK_AGENT_DIR,
18154
+ basename: "slack-channel.lock"
18155
+ });
18156
+ if (lockResult.kind === "blocked") {
18157
+ process.stderr.write(
18158
+ `slack-channel: another instance pid=${lockResult.holder.pid} already running (since ${lockResult.holder.started_at}), exiting
18159
+ `
18160
+ );
18161
+ process.exit(0);
18162
+ } else if (lockResult.kind === "acquired") {
18163
+ acquiredLockPath = lockResult.path;
18164
+ }
18165
+ }
18077
18166
  if (THREAD_STORE_PATH) {
18078
18167
  const threadStoreLabel = "slack-tracked-threads.json";
18079
18168
  const redact = (msg) => msg.replaceAll(THREAD_STORE_PATH, threadStoreLabel);