clisbot 0.1.22 → 0.1.26

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/README.md CHANGED
@@ -111,6 +111,8 @@ bun install
111
111
  bun run start --cli codex --bot-type personal --telegram-bot-token <your-telegram-bot-token> --persist
112
112
  ```
113
113
 
114
+ Repo-local `bun run start|stop|restart|status|logs|init|pairing` is pinned by `.env` to `CLISBOT_HOME=~/.clisbot-dev`, so local testing does not accidentally reuse your main `~/.clisbot` runtime.
115
+
114
116
  First conversation path:
115
117
 
116
118
  - send a DM to the bot in Slack or Telegram
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "session": {
10
10
  "mainKey": "main",
11
- "dmScope": "main",
11
+ "dmScope": "per-channel-peer",
12
12
  "identityLinks": {},
13
13
  "storePath": "~/.clisbot/state/sessions.json"
14
14
  },
@@ -154,11 +154,15 @@
154
154
  "maxActiveLoops": 10,
155
155
  "defaultTimezone": "UTC"
156
156
  },
157
- "runtimeMonitor": {
158
- "restartBackoff": {
159
- "stages": [
160
- {
161
- "delayMinutes": 15,
157
+ "runtimeMonitor": {
158
+ "restartBackoff": {
159
+ "fastRetry": {
160
+ "delaySeconds": 10,
161
+ "maxRestarts": 3
162
+ },
163
+ "stages": [
164
+ {
165
+ "delayMinutes": 15,
162
166
  "maxRestarts": 4
163
167
  },
164
168
  {
@@ -290,4 +294,4 @@
290
294
  }
291
295
  }
292
296
  }
293
- }
297
+ }
package/dist/main.js CHANGED
@@ -59932,7 +59932,7 @@ var sessionDmScopeSchema = exports_external.enum([
59932
59932
  ]);
59933
59933
  var sessionConfigSchema = exports_external.object({
59934
59934
  mainKey: exports_external.string().min(1).default("main"),
59935
- dmScope: sessionDmScopeSchema.default("main"),
59935
+ dmScope: sessionDmScopeSchema.default("per-channel-peer"),
59936
59936
  identityLinks: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).default({}),
59937
59937
  storePath: exports_external.string().default("~/.clisbot/state/sessions.json")
59938
59938
  });
@@ -60250,7 +60250,15 @@ var controlRuntimeMonitorRestartStageSchema = exports_external.object({
60250
60250
  delayMinutes: exports_external.number().int().positive().default(15),
60251
60251
  maxRestarts: exports_external.number().int().positive().default(4)
60252
60252
  });
60253
+ var controlRuntimeMonitorFastRetrySchema = exports_external.object({
60254
+ delaySeconds: exports_external.number().int().positive().default(10),
60255
+ maxRestarts: exports_external.number().int().min(0).default(3)
60256
+ });
60253
60257
  var controlRuntimeMonitorRestartBackoffSchema = exports_external.object({
60258
+ fastRetry: controlRuntimeMonitorFastRetrySchema.default({
60259
+ delaySeconds: 10,
60260
+ maxRestarts: 3
60261
+ }),
60254
60262
  stages: exports_external.array(controlRuntimeMonitorRestartStageSchema).min(1).default([
60255
60263
  {
60256
60264
  delayMinutes: 15,
@@ -60268,6 +60276,10 @@ var controlRuntimeMonitorOwnerAlertsSchema = exports_external.object({
60268
60276
  });
60269
60277
  var controlRuntimeMonitorSchema = exports_external.object({
60270
60278
  restartBackoff: controlRuntimeMonitorRestartBackoffSchema.default({
60279
+ fastRetry: {
60280
+ delaySeconds: 10,
60281
+ maxRestarts: 3
60282
+ },
60271
60283
  stages: [
60272
60284
  {
60273
60285
  delayMinutes: 15,
@@ -60328,7 +60340,7 @@ var clisbotConfigSchema = exports_external.object({
60328
60340
  }),
60329
60341
  session: sessionConfigSchema.default({
60330
60342
  mainKey: "main",
60331
- dmScope: "main",
60343
+ dmScope: "per-channel-peer",
60332
60344
  identityLinks: {},
60333
60345
  storePath: "~/.clisbot/state/sessions.json"
60334
60346
  }),
@@ -60367,6 +60379,10 @@ var clisbotConfigSchema = exports_external.object({
60367
60379
  },
60368
60380
  runtimeMonitor: {
60369
60381
  restartBackoff: {
60382
+ fastRetry: {
60383
+ delaySeconds: 10,
60384
+ maxRestarts: 3
60385
+ },
60370
60386
  stages: [
60371
60387
  {
60372
60388
  delayMinutes: 15,
@@ -60581,7 +60597,7 @@ function renderDefaultConfigTemplate(options = {}) {
60581
60597
  },
60582
60598
  session: {
60583
60599
  mainKey: "main",
60584
- dmScope: "main",
60600
+ dmScope: "per-channel-peer",
60585
60601
  identityLinks: {},
60586
60602
  storePath: sessionStorePath
60587
60603
  },
@@ -60691,6 +60707,10 @@ function renderDefaultConfigTemplate(options = {}) {
60691
60707
  },
60692
60708
  runtimeMonitor: {
60693
60709
  restartBackoff: {
60710
+ fastRetry: {
60711
+ delaySeconds: 10,
60712
+ maxRestarts: 3
60713
+ },
60694
60714
  stages: [
60695
60715
  {
60696
60716
  delayMinutes: 15,
@@ -61731,8 +61751,8 @@ class RuntimeHealthStore {
61731
61751
 
61732
61752
  // src/control/runtime-process.ts
61733
61753
  import { execFileSync, spawn as spawn3 } from "node:child_process";
61734
- import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
61735
- import { dirname as dirname13 } from "node:path";
61754
+ import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
61755
+ import { dirname as dirname13, join as join11 } from "node:path";
61736
61756
  import { kill as kill2 } from "node:process";
61737
61757
 
61738
61758
  // src/control/clisbot-wrapper.ts
@@ -62719,43 +62739,151 @@ function hasActiveRuntime(entry) {
62719
62739
  }
62720
62740
 
62721
62741
  // src/channels/agent-prompt.ts
62722
- function buildAgentPromptText(params) {
62723
- if (!params.config.enabled) {
62724
- return params.text;
62725
- }
62726
- const systemBlock = renderAgentPromptInstruction(params);
62727
- return `<system>
62728
- ${systemBlock}
62742
+ var CONFIGURATION_GUIDANCE = "When the user asks to change clisbot configuration, use clisbot CLI commands; see `clisbot --help`, `clisbot channels --help`, or `clisbot auth --help` for details.";
62743
+ var BASE_TEMPLATE = `<system>
62744
+ [{{timestamp}}] {{identity_summary}}
62745
+
62746
+ You are operating inside clisbot.
62747
+ {{delivery_intro}}
62748
+ {{reply_command}}
62749
+ {{reply_rules}}
62750
+ ${CONFIGURATION_GUIDANCE}{{protected_control_suffix}}
62751
+ </system>
62752
+
62753
+ <user>
62754
+ {{message_body}}
62755
+ </user>`;
62756
+ var STEERING_TEMPLATE = `<system>
62757
+ A new user message arrived while you were still working.
62758
+ Adjust your current work if needed and continue.{{protected_control_suffix}}
62729
62759
  </system>
62730
62760
 
62731
62761
  <user>
62732
- ${params.text}
62762
+ {{message_body}}
62733
62763
  </user>`;
62764
+ var DELIVERY_INTRO = "To send a user-visible {{progress_phrase}}final reply, use the following CLI command:";
62765
+ var DELIVERY_INTRO_CAPTURE_PANE = "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send";
62766
+ var REPLY_COMMAND = `{{reply_command_base}}
62767
+ --final{{progress_flag_suffix}} \\
62768
+ --message "$(cat <<\\__CLISBOT_MESSAGE__
62769
+ <user-facing reply>
62770
+ __CLISBOT_MESSAGE__
62771
+ )" \\
62772
+ [--media /absolute/path/to/file]`;
62773
+ var REPLY_RULES = `When replying to the user:
62774
+ - put the user-facing message inside the --message body of that command
62775
+ {{progress_rules_block}}- {{final_rule_line}}`;
62776
+ var PROGRESS_PHRASE = "progress update or ";
62777
+ var EMPTY_PROGRESS_PHRASE = "";
62778
+ var PROGRESS_FLAG_SUFFIX = "|progress";
62779
+ var EMPTY_PROGRESS_FLAG_SUFFIX = "";
62780
+ var PROGRESS_RULES_BLOCK = `- use that command to send progress updates and the final reply back to the conversation
62781
+ - send at most {{max_progress_messages}} progress updates
62782
+ - keep progress updates short and meaningful
62783
+ - do not send progress updates for trivial internal steps
62784
+ `;
62785
+ var FINAL_ONLY_RULES_BLOCK = `- use that command only for the final user-facing reply
62786
+ - do not send user-facing progress updates for this conversation
62787
+ `;
62788
+ var FINAL_RULE_REQUIRED = "send exactly 1 final user-facing response";
62789
+ var FINAL_RULE_OPTIONAL = "final response is optional";
62790
+ var EMPTY_REPLY_COMMAND = "";
62791
+ var EMPTY_REPLY_RULES = "";
62792
+ var SLACK_REPLY_COMMAND_BASE = `{{command}} message send \\
62793
+ --channel slack \\
62794
+ {{account_clause}} --target channel:{{channel_id}} \\
62795
+ {{thread_clause}}`;
62796
+ var TELEGRAM_REPLY_COMMAND_BASE = `{{command}} message send \\
62797
+ --channel telegram \\
62798
+ {{account_clause}} --target {{chat_id}} \\
62799
+ {{thread_clause}}`;
62800
+ var ACCOUNT_CLAUSE = " --account {{account_id}} \\\n";
62801
+ var EMPTY_ACCOUNT_CLAUSE = "";
62802
+ var SLACK_THREAD_CLAUSE = " --thread-id {{thread_ts}} \\\n";
62803
+ var TELEGRAM_THREAD_CLAUSE = " --thread-id {{topic_id}} \\\n";
62804
+ var EMPTY_THREAD_CLAUSE = "";
62805
+ function buildAgentPromptText(params) {
62806
+ return buildChannelPromptText({
62807
+ ...params,
62808
+ mode: "message"
62809
+ });
62734
62810
  }
62735
- function renderAgentPromptInstruction(params) {
62736
- const messageToolMode = (params.responseMode ?? "message-tool") === "message-tool";
62737
- const progressAllowed = messageToolMode && (params.streaming ?? "off") === "off";
62738
- const lines = [
62739
- `[${renderPromptTimestamp()}] ${renderIdentitySummary(params.identity)}`,
62740
- "",
62741
- "You are operating inside clisbot.",
62742
- messageToolMode ? progressAllowed ? "To send a user-visible progress update or final reply, use the following CLI command:" : "To send the final user-visible reply, use the following CLI command:" : "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send"
62743
- ];
62744
- if (messageToolMode) {
62745
- const replyCommand = buildReplyCommand({
62746
- command: getClisbotPromptCommand(),
62747
- identity: params.identity
62811
+ function buildSteeringPromptText(params) {
62812
+ return buildChannelPromptText({
62813
+ text: params.text,
62814
+ mode: "steer",
62815
+ protectedControlMutationRule: params.protectedControlMutationRule
62816
+ });
62817
+ }
62818
+ function buildChannelPromptText(params) {
62819
+ if (params.mode === "message" && !params.config?.enabled) {
62820
+ return params.text;
62821
+ }
62822
+ if (params.mode === "steer") {
62823
+ return renderTemplate(STEERING_TEMPLATE, {
62824
+ message_body: params.text,
62825
+ protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule)
62748
62826
  });
62749
- lines.push(replyCommand, "When replying to the user:", "- put the user-facing message inside the --message body of that command", progressAllowed ? "- use that command to send progress updates and the final reply back to the conversation" : "- use that command only for the final user-facing reply", ...progressAllowed ? [`- send at most ${params.config.maxProgressMessages} progress updates`] : ["- do not send user-facing progress updates for this conversation"], params.config.requireFinalResponse ? "- send exactly 1 final user-facing response" : "- final response is optional", ...progressAllowed ? [
62750
- "- keep progress updates short and meaningful",
62751
- "- do not send progress updates for trivial internal steps"
62752
- ] : []);
62753
62827
  }
62754
- if (params.protectedControlMutationRule) {
62755
- lines.push("", params.protectedControlMutationRule);
62828
+ const promptParts = renderMessagePromptParts({
62829
+ identity: params.identity,
62830
+ config: params.config,
62831
+ responseMode: params.responseMode,
62832
+ streaming: params.streaming
62833
+ });
62834
+ return renderTemplate(BASE_TEMPLATE, {
62835
+ timestamp: renderPromptTimestamp(),
62836
+ identity_summary: renderIdentitySummary(params.identity),
62837
+ delivery_intro: promptParts.deliveryIntro,
62838
+ reply_command: promptParts.replyCommand,
62839
+ reply_rules: promptParts.replyRules,
62840
+ protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule),
62841
+ message_body: params.text
62842
+ });
62843
+ }
62844
+ function renderMessagePromptParts(params) {
62845
+ const messageToolMode = (params.responseMode ?? "message-tool") === "message-tool";
62846
+ if (!messageToolMode) {
62847
+ return {
62848
+ deliveryIntro: DELIVERY_INTRO_CAPTURE_PANE,
62849
+ replyCommand: EMPTY_REPLY_COMMAND,
62850
+ replyRules: EMPTY_REPLY_RULES
62851
+ };
62756
62852
  }
62757
- return lines.join(`
62758
- `);
62853
+ const allowProgress = (params.streaming ?? "off") === "off";
62854
+ const progressPhrase = allowProgress ? PROGRESS_PHRASE : EMPTY_PROGRESS_PHRASE;
62855
+ const progressFlagSuffix = allowProgress ? PROGRESS_FLAG_SUFFIX : EMPTY_PROGRESS_FLAG_SUFFIX;
62856
+ const progressRulesBlock = allowProgress ? PROGRESS_RULES_BLOCK : FINAL_ONLY_RULES_BLOCK;
62857
+ const finalRuleLine = params.config.requireFinalResponse ? FINAL_RULE_REQUIRED : FINAL_RULE_OPTIONAL;
62858
+ return {
62859
+ deliveryIntro: renderTemplate(DELIVERY_INTRO, {
62860
+ progress_phrase: progressPhrase
62861
+ }),
62862
+ replyCommand: renderTemplate(REPLY_COMMAND, {
62863
+ reply_command_base: buildReplyCommandBase({
62864
+ command: getClisbotPromptCommand(),
62865
+ identity: params.identity
62866
+ }).trimEnd(),
62867
+ progress_flag_suffix: progressFlagSuffix
62868
+ }),
62869
+ replyRules: renderTemplate(REPLY_RULES, {
62870
+ progress_rules_block: renderTemplate(progressRulesBlock, {
62871
+ max_progress_messages: String(params.config.maxProgressMessages)
62872
+ }),
62873
+ final_rule_line: finalRuleLine
62874
+ })
62875
+ };
62876
+ }
62877
+ function renderProtectedControlSuffix(rule) {
62878
+ if (!rule) {
62879
+ return "";
62880
+ }
62881
+ return `
62882
+
62883
+ ${rule}`;
62884
+ }
62885
+ function renderTemplate(template, values) {
62886
+ return template.replaceAll(/\{\{([a-zA-Z0-9_]+)\}\}/g, (_, key) => values[key] ?? "");
62759
62887
  }
62760
62888
  function renderPromptTimestamp() {
62761
62889
  const date = new Date;
@@ -62821,42 +62949,29 @@ function renderNamedValue(label, name, id) {
62821
62949
  const value = renderLabeledTarget(name, id);
62822
62950
  return value ? `${label} ${value}` : "";
62823
62951
  }
62824
- function buildReplyCommand(params) {
62825
- const lines = [`${params.command} message send \\`];
62952
+ function buildReplyCommandBase(params) {
62826
62953
  if (params.identity.platform === "slack") {
62827
- lines.push(" --channel slack \\");
62828
- if (params.identity.accountId) {
62829
- lines.push(` --account ${params.identity.accountId} \\`);
62830
- }
62831
- lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
62832
- if (params.identity.threadTs) {
62833
- lines.push(` --thread-id ${params.identity.threadTs} \\`);
62834
- }
62835
- lines.push(" --final \\");
62836
- lines.push(' --message "$(cat <<\\__CLISBOT_MESSAGE__');
62837
- lines.push("<user-facing reply>");
62838
- lines.push("__CLISBOT_MESSAGE__");
62839
- lines.push(')" \\');
62840
- lines.push(" [--media /absolute/path/to/file]");
62841
- return lines.join(`
62842
- `);
62843
- }
62844
- lines.push(" --channel telegram \\");
62845
- if (params.identity.accountId) {
62846
- lines.push(` --account ${params.identity.accountId} \\`);
62954
+ return renderTemplate(SLACK_REPLY_COMMAND_BASE, {
62955
+ command: params.command,
62956
+ account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
62957
+ account_id: params.identity.accountId
62958
+ }) : EMPTY_ACCOUNT_CLAUSE,
62959
+ channel_id: params.identity.channelId ?? "",
62960
+ thread_clause: params.identity.threadTs ? renderTemplate(SLACK_THREAD_CLAUSE, {
62961
+ thread_ts: params.identity.threadTs
62962
+ }) : EMPTY_THREAD_CLAUSE
62963
+ });
62847
62964
  }
62848
- lines.push(` --target ${params.identity.chatId ?? ""} \\`);
62849
- if (params.identity.topicId) {
62850
- lines.push(` --thread-id ${params.identity.topicId} \\`);
62851
- }
62852
- lines.push(" --final \\");
62853
- lines.push(' --message "$(cat <<\\__CLISBOT_MESSAGE__');
62854
- lines.push("<user-facing reply>");
62855
- lines.push("__CLISBOT_MESSAGE__");
62856
- lines.push(')" \\');
62857
- lines.push(" [--media /absolute/path/to/file]");
62858
- return lines.join(`
62859
- `);
62965
+ return renderTemplate(TELEGRAM_REPLY_COMMAND_BASE, {
62966
+ command: params.command,
62967
+ account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
62968
+ account_id: params.identity.accountId
62969
+ }) : EMPTY_ACCOUNT_CLAUSE,
62970
+ chat_id: params.identity.chatId ?? "",
62971
+ thread_clause: params.identity.topicId ? renderTemplate(TELEGRAM_THREAD_CLAUSE, {
62972
+ topic_id: params.identity.topicId
62973
+ }) : EMPTY_THREAD_CLAUSE
62974
+ });
62860
62975
  }
62861
62976
 
62862
62977
  // src/channels/surface-notifications.ts
@@ -63940,6 +64055,36 @@ function renderInteractionBody(params) {
63940
64055
  }
63941
64056
  return truncateTail(completedBody, params.maxChars);
63942
64057
  }
64058
+ function normalizeLeadingStatusLine(line) {
64059
+ return line.trim().replace(/^[_*`\s]+|[_*`\s]+$/g, "");
64060
+ }
64061
+ function startsWithExplicitErrorLabel(body) {
64062
+ const firstLine = body.split(`
64063
+ `).map((line) => normalizeLeadingStatusLine(line)).find((line) => line.length > 0);
64064
+ if (!firstLine) {
64065
+ return false;
64066
+ }
64067
+ return /^(error|failed|failure|denied|forbidden)(?::|\.)/i.test(firstLine);
64068
+ }
64069
+ function shouldInlineErrorPrefix(body) {
64070
+ return !body.includes(`
64071
+ `) && !body.includes("```");
64072
+ }
64073
+ function renderErrorInteractionBody(body, footer) {
64074
+ const trimmedBody = body.trim();
64075
+ if (!trimmedBody) {
64076
+ return footer;
64077
+ }
64078
+ if (startsWithExplicitErrorLabel(trimmedBody)) {
64079
+ return trimmedBody;
64080
+ }
64081
+ if (shouldInlineErrorPrefix(trimmedBody)) {
64082
+ return `Error: ${trimmedBody}`;
64083
+ }
64084
+ return `${trimmedBody}
64085
+
64086
+ ${footer}`;
64087
+ }
63943
64088
  function renderSlackInteraction(params) {
63944
64089
  const body = renderInteractionBody(params);
63945
64090
  if (params.status === "queued") {
@@ -63967,9 +64112,7 @@ _Timed out waiting for more output._` : "_Timed out waiting for visible output._
63967
64112
  _${note}_` : `_${note}_`;
63968
64113
  }
63969
64114
  if (params.status === "error") {
63970
- return body ? `${body}
63971
-
63972
- _Error._` : "_Error._";
64115
+ return renderErrorInteractionBody(body, "_Error._");
63973
64116
  }
63974
64117
  return body || "_Completed with no new visible output._";
63975
64118
  }
@@ -64000,9 +64143,7 @@ Timed out waiting for more output.` : "Timed out waiting for visible output.";
64000
64143
  ${note}` : note;
64001
64144
  }
64002
64145
  if (params.status === "error") {
64003
- return body ? `${body}
64004
-
64005
- Error.` : "Error.";
64146
+ return renderErrorInteractionBody(body, "Error.");
64006
64147
  }
64007
64148
  return body || "Completed with no new visible output.";
64008
64149
  }
@@ -64936,15 +65077,19 @@ class RunnerService {
64936
65077
  }
64937
65078
 
64938
65079
  // src/agents/run-recovery.ts
65080
+ var MID_RUN_RECOVERY_MAX_ATTEMPTS = 2;
65081
+ var MID_RUN_RECOVERY_CONTINUE_PROMPT = "continue exactly where you left off";
64939
65082
  function mergeRunSnapshot(snapshotPrefix, snapshot) {
64940
65083
  return appendInteractionText(snapshotPrefix, snapshot);
64941
65084
  }
64942
- function buildRunRecoveryNote(kind) {
65085
+ function buildRunRecoveryNote(kind, params) {
64943
65086
  if (kind === "resume-attempt") {
64944
- return "Runner session was lost. Attempting recovery 1/2 by reopening the same conversation context.";
65087
+ const attempt = params?.attempt ?? 1;
65088
+ const maxAttempts = params?.maxAttempts ?? MID_RUN_RECOVERY_MAX_ATTEMPTS;
65089
+ return `Runner session was lost. Attempting recovery ${attempt}/${maxAttempts} by reopening the same conversation context.`;
64945
65090
  }
64946
65091
  if (kind === "resume-success") {
64947
- return "Recovery succeeded. Continuing the current run.";
65092
+ return "Recovery succeeded. Asking the runner to continue exactly where it left off.";
64948
65093
  }
64949
65094
  if (kind === "fresh-attempt") {
64950
65095
  return "The previous runner session could not be resumed. Opening a fresh runner session 2/2 without replaying your prompt.";
@@ -65416,10 +65561,13 @@ class SessionService {
65416
65561
  agentId: run.resolved.agentId,
65417
65562
  sessionKey: run.resolved.sessionKey
65418
65563
  };
65564
+ const recoveryAttempt = params.recoveryAttempt ?? 1;
65419
65565
  const snapshotPrefix = run.latestUpdate.snapshot;
65420
- const previousFullSnapshot = run.latestUpdate.fullSnapshot;
65421
65566
  const detachedAlready = run.latestUpdate.status === "detached";
65422
- await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt"));
65567
+ await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt", {
65568
+ attempt: recoveryAttempt,
65569
+ maxAttempts: MID_RUN_RECOVERY_MAX_ATTEMPTS
65570
+ }));
65423
65571
  try {
65424
65572
  const recovered = await this.runnerSessions.reopenRunContext(target, params.timingContext);
65425
65573
  const currentRun = this.activeRuns.get(sessionKey);
@@ -65438,15 +65586,22 @@ class SessionService {
65438
65586
  });
65439
65587
  await this.notifyRunObservers(currentRun, currentRun.latestUpdate);
65440
65588
  this.startRunMonitor(sessionKey, {
65441
- prompt: undefined,
65442
- initialSnapshot: previousFullSnapshot,
65589
+ prompt: MID_RUN_RECOVERY_CONTINUE_PROMPT,
65590
+ initialSnapshot: recovered.initialSnapshot,
65443
65591
  startedAt: currentRun.startedAt,
65444
65592
  detachedAlready,
65445
65593
  timingContext: params.timingContext,
65446
- snapshotPrefix
65594
+ snapshotPrefix,
65595
+ recoveryAttempt
65447
65596
  });
65448
65597
  return true;
65449
- } catch {
65598
+ } catch (reopenError) {
65599
+ if (recoveryAttempt < MID_RUN_RECOVERY_MAX_ATTEMPTS && this.runnerSessions.canRecoverMidRun(reopenError)) {
65600
+ return await this.recoverLostMidRun(sessionKey, {
65601
+ timingContext: params.timingContext,
65602
+ recoveryAttempt: recoveryAttempt + 1
65603
+ }, reopenError);
65604
+ }
65450
65605
  const currentRun = this.activeRuns.get(sessionKey);
65451
65606
  if (!currentRun) {
65452
65607
  return true;
@@ -65552,7 +65707,10 @@ class SessionService {
65552
65707
  }
65553
65708
  });
65554
65709
  } catch (error) {
65555
- if (await this.recoverLostMidRun(sessionKey, { timingContext: params.timingContext }, error)) {
65710
+ if (await this.recoverLostMidRun(sessionKey, {
65711
+ timingContext: params.timingContext,
65712
+ recoveryAttempt: (params.recoveryAttempt ?? 0) + 1
65713
+ }, error)) {
65556
65714
  return;
65557
65715
  }
65558
65716
  await this.failActiveRun(sessionKey, await this.runnerSessions.mapRunError(error, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
@@ -66877,25 +67035,6 @@ function buildChannelObserverId(identity) {
66877
67035
  identity.topicId ?? ""
66878
67036
  ].join(":");
66879
67037
  }
66880
- function buildSteeringMessage(text, protectedControlMutationRule) {
66881
- const systemLines = [
66882
- "A new user message arrived while you were still working.",
66883
- "Adjust your current work if needed and continue."
66884
- ];
66885
- if (protectedControlMutationRule) {
66886
- systemLines.push("", protectedControlMutationRule);
66887
- }
66888
- return [
66889
- "<system>",
66890
- ...systemLines,
66891
- "</system>",
66892
- "",
66893
- "<user>",
66894
- text,
66895
- "</user>"
66896
- ].join(`
66897
- `);
66898
- }
66899
67038
  function renderQueuedMessagesList(items) {
66900
67039
  if (items.length === 0) {
66901
67040
  return "Queue is empty.";
@@ -67943,7 +68082,10 @@ ${escapeCodeFence(shellResult.output)}
67943
68082
  await params.agentService.recordConversationReply(params.sessionTarget);
67944
68083
  return interactionResult;
67945
68084
  }
67946
- await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringMessage(explicitSteerMessage, params.protectedControlMutationRule));
68085
+ await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
68086
+ text: explicitSteerMessage,
68087
+ protectedControlMutationRule: params.protectedControlMutationRule
68088
+ }));
67947
68089
  await params.postText("Steered.");
67948
68090
  await params.agentService.recordConversationReply(params.sessionTarget);
67949
68091
  return {
@@ -67952,7 +68094,10 @@ ${escapeCodeFence(shellResult.output)}
67952
68094
  }
67953
68095
  if (!forceQueuedDelivery && params.route.additionalMessageMode === "steer") {
67954
68096
  if (sessionBusy && canSteerActiveRun) {
67955
- await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringMessage(params.text, params.protectedControlMutationRule));
68097
+ await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
68098
+ text: params.text,
68099
+ protectedControlMutationRule: params.protectedControlMutationRule
68100
+ }));
67956
68101
  return {
67957
68102
  processingIndicatorLifecycle: "active-run"
67958
68103
  };
@@ -68126,6 +68271,43 @@ function resolveChannelAuth(params) {
68126
68271
 
68127
68272
  // src/auth/owner-claim.ts
68128
68273
  var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
68274
+ import { statSync as statSync3 } from "node:fs";
68275
+
68276
+ // src/control/config-reload-suppression.ts
68277
+ var suppressedReloads = new Map;
68278
+ var MTIME_MATCH_EPSILON_MS = 1;
68279
+ function normalizeConfigPath(configPath) {
68280
+ return configPath.trim();
68281
+ }
68282
+ function suppressConfigReload(configPath, mtimeMs) {
68283
+ const normalizedPath = normalizeConfigPath(configPath);
68284
+ if (!normalizedPath || !Number.isFinite(mtimeMs)) {
68285
+ return;
68286
+ }
68287
+ const existing = suppressedReloads.get(normalizedPath) ?? [];
68288
+ existing.push(mtimeMs);
68289
+ suppressedReloads.set(normalizedPath, existing);
68290
+ }
68291
+ function consumeSuppressedConfigReload(configPath, mtimeMs) {
68292
+ const normalizedPath = normalizeConfigPath(configPath);
68293
+ if (!normalizedPath || !Number.isFinite(mtimeMs)) {
68294
+ return false;
68295
+ }
68296
+ const existing = suppressedReloads.get(normalizedPath);
68297
+ if (!existing || existing.length === 0) {
68298
+ return false;
68299
+ }
68300
+ const next = existing.filter((candidate) => Math.abs(candidate - mtimeMs) > MTIME_MATCH_EPSILON_MS);
68301
+ const matched = next.length !== existing.length;
68302
+ if (next.length === 0) {
68303
+ suppressedReloads.delete(normalizedPath);
68304
+ } else {
68305
+ suppressedReloads.set(normalizedPath, next);
68306
+ }
68307
+ return matched;
68308
+ }
68309
+
68310
+ // src/auth/owner-claim.ts
68129
68311
  var OWNER_CLAIM_RUNTIME_STARTED_AT_MS = Date.now();
68130
68312
  var CONFIG_LOCK_OPTIONS = {
68131
68313
  retries: {
@@ -68221,6 +68403,7 @@ async function claimFirstOwnerFromDirectMessage(params) {
68221
68403
  }
68222
68404
  freshConfig.app.auth.roles.owner.users = [...currentOwners, principal];
68223
68405
  await writeEditableConfig(expandedPath, freshConfig);
68406
+ suppressConfigReload(expandedPath, statSync3(expandedPath).mtimeMs);
68224
68407
  syncOwnerUsers(params.config, freshConfig);
68225
68408
  ownerClaimRuntimeState.closed = true;
68226
68409
  console.log(`clisbot auto-claimed first owner ${principal}`);
@@ -68726,6 +68909,14 @@ function stripBotMention(text, botUserId) {
68726
68909
  }
68727
68910
  return text.replaceAll(`<@${botUserId}>`, "").replaceAll(/<@[^>]+>/g, "").trim();
68728
68911
  }
68912
+ function resolveSlackDirectReplyThreadTs(params) {
68913
+ const resolvedThreadTs = (params.resolvedThreadTs ?? "").trim();
68914
+ if (resolvedThreadTs) {
68915
+ return resolvedThreadTs;
68916
+ }
68917
+ const messageTs = (params.messageTs ?? "").trim();
68918
+ return messageTs || undefined;
68919
+ }
68729
68920
 
68730
68921
  // src/channels/slack/reactions.ts
68731
68922
  function normalizeSlackReactionName(value) {
@@ -69501,6 +69692,10 @@ class SlackSocketService {
69501
69692
  await this.processedEventsStore.markCompleted(eventId);
69502
69693
  return;
69503
69694
  }
69695
+ const directReplyThreadTs = resolveSlackDirectReplyThreadTs({
69696
+ messageTs,
69697
+ resolvedThreadTs: await this.resolveThreadTs(event)
69698
+ });
69504
69699
  let ownerClaimed = false;
69505
69700
  let ownerPrincipal;
69506
69701
  try {
@@ -69518,6 +69713,7 @@ class SlackSocketService {
69518
69713
  try {
69519
69714
  await postSlackText(this.app.client, {
69520
69715
  channel: channelId,
69716
+ threadTs: directReplyThreadTs,
69521
69717
  text: renderFirstOwnerClaimMessage({
69522
69718
  principal: ownerPrincipal,
69523
69719
  ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
@@ -69548,6 +69744,7 @@ class SlackSocketService {
69548
69744
  try {
69549
69745
  await postSlackText(this.app.client, {
69550
69746
  channel: channelId,
69747
+ threadTs: directReplyThreadTs,
69551
69748
  text: buildPairingReply({
69552
69749
  channel: "slack",
69553
69750
  idLine: `Your Slack user id: ${directUserId}`,
@@ -71123,6 +71320,7 @@ class TelegramPollingService {
71123
71320
  try {
71124
71321
  await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
71125
71322
  chat_id: message.chat.id,
71323
+ ...message.message_id != null ? { reply_to_message_id: message.message_id } : {},
71126
71324
  text: renderFirstOwnerClaimMessage({
71127
71325
  principal: ownerPrincipal,
71128
71326
  ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
@@ -71809,19 +72007,33 @@ function summarizeExit(params) {
71809
72007
  return `code ${params.code ?? 0}`;
71810
72008
  }
71811
72009
  function getRestartPlan(config, restartNumber) {
71812
- let completedRestarts = 0;
71813
- const totalRestarts = config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
72010
+ const fastRetryMaxRestarts = config.fastRetry.maxRestarts;
72011
+ const totalRestarts = fastRetryMaxRestarts + config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
72012
+ if (restartNumber >= 1 && restartNumber <= fastRetryMaxRestarts) {
72013
+ return {
72014
+ mode: "fast-retry",
72015
+ stageIndex: -1,
72016
+ delayMs: config.fastRetry.delaySeconds * 1000,
72017
+ restartAttemptInStage: restartNumber,
72018
+ restartsRemaining: totalRestarts - restartNumber,
72019
+ totalRestarts,
72020
+ stageMaxRestarts: fastRetryMaxRestarts
72021
+ };
72022
+ }
72023
+ let completedRestarts = fastRetryMaxRestarts;
71814
72024
  for (let index = 0;index < config.stages.length; index += 1) {
71815
72025
  const stage = config.stages[index];
71816
72026
  const stageStart = completedRestarts + 1;
71817
72027
  const stageEnd = completedRestarts + stage.maxRestarts;
71818
72028
  if (restartNumber >= stageStart && restartNumber <= stageEnd) {
71819
72029
  return {
72030
+ mode: "backoff",
71820
72031
  stageIndex: index,
71821
- delayMinutes: stage.delayMinutes,
72032
+ delayMs: stage.delayMinutes * 60000,
71822
72033
  restartAttemptInStage: restartNumber - completedRestarts,
71823
72034
  restartsRemaining: totalRestarts - restartNumber,
71824
- totalRestarts
72035
+ totalRestarts,
72036
+ stageMaxRestarts: stage.maxRestarts
71825
72037
  };
71826
72038
  }
71827
72039
  completedRestarts = stageEnd;
@@ -71955,7 +72167,7 @@ function renderBackoffAlertMessage(params) {
71955
72167
  `next restart: ${params.nextRestartAt}`,
71956
72168
  `restart: ${params.restartNumber}/${params.totalRestarts}`,
71957
72169
  `stage: ${params.stageIndex + 1}/${params.config.restartBackoff.stages.length}`,
71958
- `stage attempt: ${params.restartAttemptInStage}/${params.config.restartBackoff.stages[params.stageIndex]?.maxRestarts ?? params.restartAttemptInStage}`
72170
+ `stage attempt: ${params.restartAttemptInStage}/${params.stageMaxRestarts}`
71959
72171
  ].join(`
71960
72172
  `);
71961
72173
  }
@@ -72048,20 +72260,23 @@ class RuntimeMonitor {
72048
72260
  }
72049
72261
  restartNumber = nextRestartNumber;
72050
72262
  totalRestarts = plan.totalRestarts;
72051
- const nextRestartAt = new Date(this.dependencies.now() + plan.delayMinutes * 60000).toISOString();
72052
- await this.maybeSendAlert("backoff", monitorConfig, renderBackoffAlertMessage({
72053
- config: monitorConfig,
72054
- restartNumber,
72055
- stageIndex: plan.stageIndex,
72056
- restartAttemptInStage: plan.restartAttemptInStage,
72057
- totalRestarts,
72058
- nextRestartAt,
72059
- exit: {
72060
- code: exit.code,
72061
- signal: exit.signal,
72062
- at: exitAt
72063
- }
72064
- }));
72263
+ const nextRestartAt = new Date(this.dependencies.now() + plan.delayMs).toISOString();
72264
+ if (plan.mode === "backoff") {
72265
+ await this.maybeSendAlert("backoff", monitorConfig, renderBackoffAlertMessage({
72266
+ config: monitorConfig,
72267
+ restartNumber,
72268
+ stageIndex: plan.stageIndex,
72269
+ restartAttemptInStage: plan.restartAttemptInStage,
72270
+ stageMaxRestarts: plan.stageMaxRestarts,
72271
+ totalRestarts,
72272
+ nextRestartAt,
72273
+ exit: {
72274
+ code: exit.code,
72275
+ signal: exit.signal,
72276
+ at: exitAt
72277
+ }
72278
+ }));
72279
+ }
72065
72280
  await this.writeState({
72066
72281
  phase: "backoff",
72067
72282
  runtimePid: undefined,
@@ -72071,6 +72286,7 @@ class RuntimeMonitor {
72071
72286
  at: exitAt
72072
72287
  },
72073
72288
  restart: {
72289
+ mode: plan.mode,
72074
72290
  stageIndex: plan.stageIndex,
72075
72291
  restartNumber,
72076
72292
  restartAttemptInStage: plan.restartAttemptInStage,
@@ -72078,7 +72294,7 @@ class RuntimeMonitor {
72078
72294
  nextRestartAt
72079
72295
  }
72080
72296
  });
72081
- await this.sleepWithStop(plan.delayMinutes * 60000);
72297
+ await this.sleepWithStop(plan.delayMs);
72082
72298
  }
72083
72299
  } finally {
72084
72300
  await this.stopActiveChild();
@@ -72239,17 +72455,87 @@ var PROCESS_POLL_INTERVAL_MS = 100;
72239
72455
  function resolveConfigPath(configPath) {
72240
72456
  return expandHomePath(configPath ?? process.env.CLISBOT_CONFIG_PATH ?? getDefaultConfigPath());
72241
72457
  }
72242
- function resolvePidPath(pidPath) {
72243
- return expandHomePath(pidPath ?? process.env.CLISBOT_PID_PATH ?? getDefaultRuntimePidPath());
72458
+ function deriveRuntimeSiblingPath(configPath, filename) {
72459
+ if (!configPath) {
72460
+ return null;
72461
+ }
72462
+ return join11(dirname13(expandHomePath(configPath)), "state", filename);
72244
72463
  }
72245
- function resolveLogPath(logPath) {
72246
- return expandHomePath(logPath ?? process.env.CLISBOT_LOG_PATH ?? getDefaultRuntimeLogPath());
72464
+ function resolvePidPath(pidPath, configPath, options = {}) {
72465
+ if (pidPath) {
72466
+ return expandHomePath(pidPath);
72467
+ }
72468
+ if (options.preferConfigSibling) {
72469
+ const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.pid");
72470
+ if (derivedFromExplicitConfig) {
72471
+ return derivedFromExplicitConfig;
72472
+ }
72473
+ }
72474
+ if (process.env.CLISBOT_PID_PATH) {
72475
+ return expandHomePath(process.env.CLISBOT_PID_PATH);
72476
+ }
72477
+ const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.pid");
72478
+ if (derivedFromConfig) {
72479
+ return derivedFromConfig;
72480
+ }
72481
+ return expandHomePath(getDefaultRuntimePidPath());
72247
72482
  }
72248
- function resolveMonitorStatePath(monitorStatePath) {
72249
- return expandHomePath(monitorStatePath ?? process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH ?? getDefaultRuntimeMonitorStatePath());
72483
+ function resolveLogPath(logPath, configPath, options = {}) {
72484
+ if (logPath) {
72485
+ return expandHomePath(logPath);
72486
+ }
72487
+ if (options.preferConfigSibling) {
72488
+ const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.log");
72489
+ if (derivedFromExplicitConfig) {
72490
+ return derivedFromExplicitConfig;
72491
+ }
72492
+ }
72493
+ if (process.env.CLISBOT_LOG_PATH) {
72494
+ return expandHomePath(process.env.CLISBOT_LOG_PATH);
72495
+ }
72496
+ const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.log");
72497
+ if (derivedFromConfig) {
72498
+ return derivedFromConfig;
72499
+ }
72500
+ return expandHomePath(getDefaultRuntimeLogPath());
72250
72501
  }
72251
- function resolveRuntimeCredentialsPath(runtimeCredentialsPath) {
72252
- return expandHomePath(runtimeCredentialsPath ?? process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH ?? getDefaultRuntimeCredentialsPath());
72502
+ function resolveMonitorStatePath(monitorStatePath, configPath, options = {}) {
72503
+ if (monitorStatePath) {
72504
+ return expandHomePath(monitorStatePath);
72505
+ }
72506
+ if (options.preferConfigSibling) {
72507
+ const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot-monitor.json");
72508
+ if (derivedFromExplicitConfig) {
72509
+ return derivedFromExplicitConfig;
72510
+ }
72511
+ }
72512
+ if (process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH) {
72513
+ return expandHomePath(process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH);
72514
+ }
72515
+ const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot-monitor.json");
72516
+ if (derivedFromConfig) {
72517
+ return derivedFromConfig;
72518
+ }
72519
+ return expandHomePath(getDefaultRuntimeMonitorStatePath());
72520
+ }
72521
+ function resolveRuntimeCredentialsPath(runtimeCredentialsPath, configPath, options = {}) {
72522
+ if (runtimeCredentialsPath) {
72523
+ return expandHomePath(runtimeCredentialsPath);
72524
+ }
72525
+ if (options.preferConfigSibling) {
72526
+ const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "runtime-credentials.json");
72527
+ if (derivedFromExplicitConfig) {
72528
+ return derivedFromExplicitConfig;
72529
+ }
72530
+ }
72531
+ if (process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH) {
72532
+ return expandHomePath(process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH);
72533
+ }
72534
+ const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "runtime-credentials.json");
72535
+ if (derivedFromConfig) {
72536
+ return derivedFromConfig;
72537
+ }
72538
+ return expandHomePath(getDefaultRuntimeCredentialsPath());
72253
72539
  }
72254
72540
 
72255
72541
  class StartDetachedRuntimeError extends Error {
@@ -72326,10 +72612,14 @@ async function ensureConfigFile(configPath, options = {}) {
72326
72612
  };
72327
72613
  }
72328
72614
  async function startDetachedRuntime(params) {
72329
- const pidPath = resolvePidPath(params.pidPath);
72330
- const logPath = resolveLogPath(params.logPath);
72331
- const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath);
72332
- const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath);
72615
+ const configPath = resolveConfigPath(params.configPath);
72616
+ const preferConfigSibling = params.configPath != null;
72617
+ const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
72618
+ const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
72619
+ const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
72620
+ preferConfigSibling
72621
+ });
72622
+ const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
72333
72623
  const existingPid = await readRuntimePid(pidPath);
72334
72624
  const existingMonitorState = await readRuntimeMonitorState(monitorStatePath);
72335
72625
  if (existingPid && isProcessRunning(existingPid)) {
@@ -72337,7 +72627,7 @@ async function startDetachedRuntime(params) {
72337
72627
  alreadyRunning: true,
72338
72628
  createdConfig: false,
72339
72629
  pid: existingPid,
72340
- configPath: resolveConfigPath(params.configPath),
72630
+ configPath,
72341
72631
  logPath
72342
72632
  };
72343
72633
  }
@@ -72402,9 +72692,13 @@ async function startDetachedRuntime(params) {
72402
72692
  };
72403
72693
  }
72404
72694
  async function stopDetachedRuntime(params, dependencies = {}) {
72405
- const pidPath = resolvePidPath(params.pidPath);
72406
- const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath);
72407
- const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath);
72695
+ const configPath = resolveConfigPath(params.configPath);
72696
+ const preferConfigSibling = params.configPath != null;
72697
+ const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
72698
+ const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
72699
+ preferConfigSibling
72700
+ });
72701
+ const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
72408
72702
  const existingPid = await readRuntimePid(pidPath);
72409
72703
  const monitorState = await readRuntimeMonitorState(monitorStatePath);
72410
72704
  let stopped = false;
@@ -72445,7 +72739,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
72445
72739
  }
72446
72740
  rmSync3(pidPath, { force: true });
72447
72741
  removeRuntimeCredentials(runtimeCredentialsPath);
72448
- await disableExpiredMemAccountsInConfig(params.configPath);
72742
+ await disableExpiredMemAccountsInConfig(configPath);
72449
72743
  if (monitorState) {
72450
72744
  await writeRuntimeMonitorState(monitorStatePath, {
72451
72745
  ...monitorState,
@@ -72456,7 +72750,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
72456
72750
  });
72457
72751
  }
72458
72752
  if (params.hard) {
72459
- const socketPath = await resolveTmuxSocketPath(params.configPath);
72753
+ const socketPath = await resolveTmuxSocketPath(configPath);
72460
72754
  const tmux = new TmuxClient(socketPath);
72461
72755
  try {
72462
72756
  await tmux.killServer();
@@ -72489,9 +72783,12 @@ function removeRuntimePid(pidPath) {
72489
72783
  }
72490
72784
  async function getRuntimeStatus(params = {}) {
72491
72785
  const configPath = resolveConfigPath(params.configPath);
72492
- const pidPath = resolvePidPath(params.pidPath);
72493
- const logPath = resolveLogPath(params.logPath);
72494
- const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath);
72786
+ const preferConfigSibling = params.configPath != null;
72787
+ const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
72788
+ const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
72789
+ const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
72790
+ preferConfigSibling
72791
+ });
72495
72792
  const pid = await readRuntimePid(pidPath);
72496
72793
  const liveness = pid ? getProcessLiveness(pid) : "missing";
72497
72794
  const monitorState = await readRuntimeMonitorState(monitorStatePath);
@@ -72508,6 +72805,7 @@ async function getRuntimeStatus(params = {}) {
72508
72805
  runtimePid: monitorState?.runtimePid && getProcessLiveness(monitorState.runtimePid) === "running" ? monitorState.runtimePid : undefined,
72509
72806
  nextRestartAt: monitorState?.restart?.nextRestartAt,
72510
72807
  restartNumber: monitorState?.restart?.restartNumber,
72808
+ restartMode: monitorState?.restart?.mode,
72511
72809
  restartStageIndex: monitorState?.restart?.stageIndex,
72512
72810
  stopReason: monitorState?.stopReason
72513
72811
  };
@@ -72545,7 +72843,7 @@ function getLogSize(logPath) {
72545
72843
  return 0;
72546
72844
  }
72547
72845
  try {
72548
- return statSync3(logPath).size;
72846
+ return statSync4(logPath).size;
72549
72847
  } catch {
72550
72848
  return 0;
72551
72849
  }
@@ -75086,7 +75384,7 @@ async function start(args = []) {
75086
75384
  }
75087
75385
 
75088
75386
  // src/control/runtime-supervisor.ts
75089
- import { statSync as statSync4, watch } from "node:fs";
75387
+ import { statSync as statSync5, watch } from "node:fs";
75090
75388
  import { basename as basename4, dirname as dirname15 } from "node:path";
75091
75389
 
75092
75390
  // src/channels/processed-events-store.ts
@@ -75249,12 +75547,22 @@ class RuntimeSupervisor {
75249
75547
  let nextRuntime;
75250
75548
  try {
75251
75549
  const loadedConfig = await this.dependencies.loadConfig(this.configPath);
75550
+ const configMtimeMs = statSync5(loadedConfig.configPath).mtimeMs;
75551
+ if (reason === "watch" && consumeSuppressedConfigReload(loadedConfig.configPath, configMtimeMs)) {
75552
+ await this.reconcileConfigWatcher(loadedConfig);
75553
+ await this.dependencies.runtimeHealthStore.setReload({
75554
+ status: "success",
75555
+ reason,
75556
+ configMtimeMs
75557
+ });
75558
+ return;
75559
+ }
75252
75560
  nextRuntime = await this.createRuntime(loadedConfig);
75253
75561
  await this.reconcileConfigWatcher(loadedConfig);
75254
75562
  await this.dependencies.runtimeHealthStore.setReload({
75255
75563
  status: "success",
75256
75564
  reason,
75257
- configMtimeMs: statSync4(loadedConfig.configPath).mtimeMs
75565
+ configMtimeMs
75258
75566
  });
75259
75567
  this.activeRuntime = nextRuntime;
75260
75568
  if (previousRuntime) {
@@ -75596,6 +75904,9 @@ async function printStatusSummary() {
75596
75904
  if (runtimeStatus.restartNumber) {
75597
75905
  console.log(`restart attempt: ${runtimeStatus.restartNumber}`);
75598
75906
  }
75907
+ if (runtimeStatus.restartMode) {
75908
+ console.log(`restart mode: ${runtimeStatus.restartMode}`);
75909
+ }
75599
75910
  if (runtimeStatus.restartStageIndex != null && runtimeStatus.restartStageIndex >= 0) {
75600
75911
  console.log(`restart stage: ${runtimeStatus.restartStageIndex + 1}`);
75601
75912
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clisbot",
3
- "version": "0.1.22",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "description": "Chat surfaces for durable AI coding agents running in tmux",
6
6
  "license": "MIT",
@@ -29,14 +29,14 @@
29
29
  "scripts": {
30
30
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && bun build ./src/main.ts --target=node --outfile ./dist/main.js",
31
31
  "dev": "bun --watch run src/main.ts serve-foreground",
32
- "start": "bun run src/main.ts start",
33
- "restart": "bun run src/main.ts restart",
34
- "stop": "bun run src/main.ts stop",
35
- "status": "bun run src/main.ts status",
36
- "logs": "bun run src/main.ts logs",
37
- "init": "bun run src/main.ts init",
32
+ "start": "bash ./scripts/run-dev-cli.sh start",
33
+ "restart": "bash ./scripts/run-dev-cli.sh restart",
34
+ "stop": "bash ./scripts/run-dev-cli.sh stop",
35
+ "status": "bash ./scripts/run-dev-cli.sh status",
36
+ "logs": "bash ./scripts/run-dev-cli.sh logs",
37
+ "init": "bash ./scripts/run-dev-cli.sh init",
38
38
  "print-config-path": "bun run src/main.ts print-config-path",
39
- "pairing": "bun run src/main.ts pairing",
39
+ "pairing": "bash ./scripts/run-dev-cli.sh pairing",
40
40
  "typecheck": "bunx tsc --noEmit",
41
41
  "test": "bun test",
42
42
  "prepack": "bun run build",