clisbot 0.1.26 → 0.1.29

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/main.js CHANGED
@@ -4,43 +4,25 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
7
  var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
8
  target = mod != null ? __create(__getProtoOf(mod)) : {};
21
9
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
10
  for (let key of __getOwnPropNames(mod))
23
11
  if (!__hasOwnProp.call(to, key))
24
12
  __defProp(to, key, {
25
- get: __accessProp.bind(mod, key),
13
+ get: () => mod[key],
26
14
  enumerable: true
27
15
  });
28
- if (canCache)
29
- cache.set(mod, to);
30
16
  return to;
31
17
  };
32
18
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
- var __returnValue = (v) => v;
34
- function __exportSetter(name, newValue) {
35
- this[name] = __returnValue.bind(null, newValue);
36
- }
37
19
  var __export = (target, all) => {
38
20
  for (var name in all)
39
21
  __defProp(target, name, {
40
22
  get: all[name],
41
23
  enumerable: true,
42
24
  configurable: true,
43
- set: __exportSetter.bind(all, name)
25
+ set: (newValue) => all[name] = () => newValue
44
26
  });
45
27
  };
46
28
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -54694,6 +54676,7 @@ function renderCliHelp() {
54694
54676
  "",
54695
54677
  "Commands:",
54696
54678
  ` start Seed ${configPath} if missing, apply explicit channel-account bootstrap intent, and start clisbot in the background.`,
54679
+ " See `clisbot start --help` for bootstrap-focused flags and examples.",
54697
54680
  " restart Stop the running clisbot process, then start it again.",
54698
54681
  " stop Stop the running clisbot process.",
54699
54682
  " stop --hard Stop clisbot and kill all tmux sessions on the configured clisbot socket.",
@@ -54710,20 +54693,26 @@ function renderCliHelp() {
54710
54693
  " remove slack-group <groupId>",
54711
54694
  " set-token <slack-app|slack-bot|telegram-bot> <value>",
54712
54695
  " clear-token <slack-app|slack-bot|telegram-bot>",
54696
+ " See `clisbot channels --help` for route policy notes and defaults such as `requireMention`.",
54713
54697
  " accounts Manage Slack and Telegram provider accounts plus persistence state.",
54714
54698
  " add telegram --account <id> --token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
54715
54699
  " add slack --account <id> --app-token <ENV_NAME|${ENV_NAME}|literal> --bot-token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
54716
54700
  " persist --channel <slack|telegram> --account <id>",
54717
54701
  " persist --all",
54702
+ " See `clisbot accounts --help` for env-vs-mem-vs-persist behavior.",
54718
54703
  " loops Inspect or cancel managed recurring loops persisted by `/loop`.",
54719
54704
  " list|status",
54720
54705
  " cancel <id>",
54721
54706
  " cancel --all",
54707
+ " See `clisbot loops --help` for behavior notes.",
54722
54708
  " message Run provider message actions such as send, react, read, edit, delete, and pins.",
54709
+ " See `clisbot message --help` for channel-specific syntax.",
54723
54710
  " agents Manage configured agents and top-level bindings.",
54711
+ " See `clisbot agents --help` for focused add/bootstrap/binding help.",
54724
54712
  " auth Manage app and agent auth roles, principals, and permissions in config. See `clisbot auth --help`.",
54725
- " pairing Run the pairing control CLI.",
54713
+ " pairing Run the pairing control CLI. See `clisbot pairing --help`.",
54726
54714
  ` init Seed ${configPath} and optionally create the first agent without starting clisbot.`,
54715
+ " See `clisbot init --help` for bootstrap-focused flags and examples.",
54727
54716
  " --version, -v Show the installed clisbot version.",
54728
54717
  " --help Show this help text.",
54729
54718
  "",
@@ -54766,6 +54755,35 @@ function buildPairingReply(params) {
54766
54755
  ].join(`
54767
54756
  `);
54768
54757
  }
54758
+ function buildPairingQueueFullReply(params) {
54759
+ return [
54760
+ "clisbot: access not configured.",
54761
+ "",
54762
+ params.idLine,
54763
+ "",
54764
+ "Pairing queue is full right now.",
54765
+ "",
54766
+ "Ask the bot owner to inspect or clear pending requests with:",
54767
+ `clisbot pairing list ${params.channel}`,
54768
+ `clisbot pairing reject ${params.channel} <code>`,
54769
+ `clisbot pairing clear ${params.channel}`
54770
+ ].join(`
54771
+ `);
54772
+ }
54773
+ function buildPairingReplyFromRequest(params) {
54774
+ const code = params.pairingRequest.code.trim();
54775
+ if (!code) {
54776
+ return buildPairingQueueFullReply({
54777
+ channel: params.channel,
54778
+ idLine: params.idLine
54779
+ });
54780
+ }
54781
+ return buildPairingReply({
54782
+ channel: params.channel,
54783
+ idLine: params.idLine,
54784
+ code
54785
+ });
54786
+ }
54769
54787
  function renderPairingRequests(params) {
54770
54788
  if (!params.requests.length) {
54771
54789
  return `No pending ${params.channel} pairing requests.`;
@@ -54788,7 +54806,7 @@ import path from "node:path";
54788
54806
  var PAIRING_CODE_LENGTH = 8;
54789
54807
  var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
54790
54808
  var PAIRING_PENDING_TTL_MS = 60 * 60 * 1000;
54791
- var PAIRING_PENDING_MAX = 3;
54809
+ var PAIRING_PENDING_MAX = 20;
54792
54810
  var PAIRING_STORE_LOCK_OPTIONS = {
54793
54811
  retries: {
54794
54812
  retries: 10,
@@ -55067,8 +55085,63 @@ async function approveChannelPairingCode(params) {
55067
55085
  return { id: entry.id, entry };
55068
55086
  });
55069
55087
  }
55088
+ async function rejectChannelPairingCode(params) {
55089
+ const code = params.code.trim().toUpperCase();
55090
+ if (!code) {
55091
+ return null;
55092
+ }
55093
+ const filePath = resolvePairingPath(params.channel, params.baseDir);
55094
+ return withFileLock(filePath, { version: 1, requests: [] }, async () => {
55095
+ const { value } = await readJsonFile(filePath, {
55096
+ version: 1,
55097
+ requests: []
55098
+ });
55099
+ const requests = Array.isArray(value.requests) ? value.requests : [];
55100
+ const { requests: pruned, removed } = pruneExpiredRequests(requests, Date.now());
55101
+ const matchIndex = pruned.findIndex((request) => String(request.code ?? "").trim().toUpperCase() === code);
55102
+ if (matchIndex < 0) {
55103
+ if (removed) {
55104
+ await writeJsonFile(filePath, {
55105
+ version: 1,
55106
+ requests: pruned
55107
+ });
55108
+ }
55109
+ return null;
55110
+ }
55111
+ const [rejected] = pruned.splice(matchIndex, 1);
55112
+ await writeJsonFile(filePath, {
55113
+ version: 1,
55114
+ requests: pruned
55115
+ });
55116
+ return rejected ?? null;
55117
+ });
55118
+ }
55119
+ async function clearChannelPairingRequests(params) {
55120
+ const filePath = resolvePairingPath(params.channel, params.baseDir);
55121
+ return withFileLock(filePath, { version: 1, requests: [] }, async () => {
55122
+ const { value } = await readJsonFile(filePath, {
55123
+ version: 1,
55124
+ requests: []
55125
+ });
55126
+ const requests = Array.isArray(value.requests) ? value.requests : [];
55127
+ const { requests: pruned } = pruneExpiredRequests(requests, Date.now());
55128
+ await writeJsonFile(filePath, {
55129
+ version: 1,
55130
+ requests: []
55131
+ });
55132
+ return { cleared: pruned.length };
55133
+ });
55134
+ }
55070
55135
 
55071
55136
  // src/channels/pairing/cli.ts
55137
+ function resolvePairingBaseDir(env = process.env) {
55138
+ const configured = env.CLISBOT_PAIRING_DIR?.trim();
55139
+ if (configured) {
55140
+ return configured;
55141
+ }
55142
+ const legacy = env.TMUX_TALK_PAIRING_DIR?.trim();
55143
+ return legacy || undefined;
55144
+ }
55072
55145
  function parseChannel(raw) {
55073
55146
  const value = raw?.trim().toLowerCase();
55074
55147
  if (value === "slack" || value === "telegram") {
@@ -55076,9 +55149,33 @@ function parseChannel(raw) {
55076
55149
  }
55077
55150
  throw new Error("Channel required: slack | telegram");
55078
55151
  }
55152
+ function renderPairingCliHelp() {
55153
+ return [
55154
+ "clisbot pairing",
55155
+ "",
55156
+ "Usage:",
55157
+ " clisbot pairing --help",
55158
+ " clisbot pairing help",
55159
+ " clisbot pairing list <slack|telegram> [--json]",
55160
+ " clisbot pairing approve <slack|telegram> <code>",
55161
+ " clisbot pairing reject <slack|telegram> <code>",
55162
+ " clisbot pairing clear <slack|telegram>",
55163
+ "",
55164
+ "Notes:",
55165
+ " - `list` shows pending pairing requests for one channel only",
55166
+ " - `approve` moves that sender into the channel allowlist",
55167
+ " - `reject` removes one pending request without allowlisting the sender",
55168
+ " - `clear` drops every pending request for that channel when the queue needs a reset"
55169
+ ].join(`
55170
+ `);
55171
+ }
55079
55172
  async function runPairingCli(args, writer = console) {
55080
55173
  const [command, ...rest] = args;
55081
- const baseDir = process.env.TMUX_TALK_PAIRING_DIR;
55174
+ const baseDir = resolvePairingBaseDir();
55175
+ if (!command || command === "--help" || command === "-h" || command === "help") {
55176
+ writer.log(renderPairingCliHelp());
55177
+ return;
55178
+ }
55082
55179
  if (command === "list") {
55083
55180
  const wantsJson = rest.includes("--json");
55084
55181
  const channel = parseChannel(rest.find((value) => !value.startsWith("--")));
@@ -55103,7 +55200,34 @@ async function runPairingCli(args, writer = console) {
55103
55200
  writer.log(`Approved ${channel} sender ${approved.id}.`);
55104
55201
  return;
55105
55202
  }
55106
- throw new Error("Usage: pairing list <channel> [--json] | pairing approve <channel> <code>");
55203
+ if (command === "reject") {
55204
+ const [channelArg, code] = rest;
55205
+ const channel = parseChannel(channelArg);
55206
+ if (!code?.trim()) {
55207
+ throw new Error("Usage: pairing reject <channel> <code>");
55208
+ }
55209
+ const rejected = await rejectChannelPairingCode({
55210
+ channel,
55211
+ code,
55212
+ baseDir
55213
+ });
55214
+ if (!rejected) {
55215
+ throw new Error(`No pending pairing request found for code: ${code}`);
55216
+ }
55217
+ writer.log(`Rejected ${channel} sender ${rejected.id}.`);
55218
+ return;
55219
+ }
55220
+ if (command === "clear") {
55221
+ const [channelArg] = rest;
55222
+ const channel = parseChannel(channelArg);
55223
+ const result = await clearChannelPairingRequests({
55224
+ channel,
55225
+ baseDir
55226
+ });
55227
+ writer.log(`Cleared ${result.cleared} pending ${channel} pairing request(s).`);
55228
+ return;
55229
+ }
55230
+ throw new Error(renderPairingCliHelp());
55107
55231
  }
55108
55232
 
55109
55233
  // src/config/agent-tool-presets.ts
@@ -59283,7 +59407,7 @@ function formatConfiguredRuntimeLimit(params) {
59283
59407
  if (typeof params.maxRuntimeMin === "number" && Number.isFinite(params.maxRuntimeMin)) {
59284
59408
  return `${params.maxRuntimeMin} minute${params.maxRuntimeMin === 1 ? "" : "s"}`;
59285
59409
  }
59286
- return "15 minutes";
59410
+ return "30 minutes";
59287
59411
  }
59288
59412
  function parseCommandDurationMs(raw) {
59289
59413
  const match = raw.trim().match(/^(\d+)(ms|s|m|h)$/i);
@@ -59991,7 +60115,7 @@ var agentDefaultsSchema = exports_external.object({
59991
60115
  updateIntervalMs: 2000,
59992
60116
  idleTimeoutMs: 6000,
59993
60117
  noOutputTimeoutMs: 20000,
59994
- maxRuntimeMin: 15,
60118
+ maxRuntimeMin: 30,
59995
60119
  maxMessageChars: 3500
59996
60120
  }),
59997
60121
  session: sessionSchema.default({
@@ -60679,7 +60803,7 @@ function renderDefaultConfigTemplate(options = {}) {
60679
60803
  updateIntervalMs: 2000,
60680
60804
  idleTimeoutMs: 6000,
60681
60805
  noOutputTimeoutMs: 20000,
60682
- maxRuntimeMin: 15,
60806
+ maxRuntimeMin: 30,
60683
60807
  maxMessageChars: 3500
60684
60808
  },
60685
60809
  session: {
@@ -61044,6 +61168,32 @@ function parseSingleOption(args, name) {
61044
61168
  function hasFlag(args, name) {
61045
61169
  return args.includes(name);
61046
61170
  }
61171
+ function renderAgentsHelp() {
61172
+ return [
61173
+ "clisbot agents",
61174
+ "",
61175
+ "Usage:",
61176
+ " clisbot agents --help",
61177
+ " clisbot agents help",
61178
+ " clisbot agents list [--bindings] [--json]",
61179
+ " clisbot agents add <id> --cli <codex|claude|gemini> [--workspace <path>] [--startup-option <arg>]... [--bootstrap <personal-assistant|team-assistant>] [--bind <channel[:accountId]>]...",
61180
+ " clisbot agents bootstrap <id> --mode <personal-assistant|team-assistant> [--force]",
61181
+ " clisbot agents bindings [--agent <id>] [--json]",
61182
+ " clisbot agents bind --agent <id> --bind <channel[:accountId]>",
61183
+ " clisbot agents unbind --agent <id> [--bind <channel[:accountId]> | --all]",
61184
+ " clisbot agents response-mode <status|set|clear> --agent <id> [capture-pane|message-tool]",
61185
+ " clisbot agents additional-message-mode <status|set|clear> --agent <id> [queue|steer]",
61186
+ "",
61187
+ "Notes:",
61188
+ " - `agents add` is the lower-level manual surface; first-run `clisbot start` and `clisbot init` can bootstrap the first `default` agent for you",
61189
+ " - `--cli` is required on `agents add`; supported tools are `codex`, `claude`, and `gemini`",
61190
+ " - omit `--startup-option` to inherit the built-in startup args for the selected CLI tool",
61191
+ " - `--bind slack`, `--bind telegram`, or `--bind <channel>:<accountId>` creates top-level fallback bindings",
61192
+ " - explicit route `agentId` on Slack or Telegram still wins before these fallback bindings",
61193
+ " - `response-mode` and `additional-message-mode` mutate per-agent overrides under `agents.list[]`"
61194
+ ].join(`
61195
+ `);
61196
+ }
61047
61197
  function parseResponseMode(raw) {
61048
61198
  if (raw === "capture-pane" || raw === "message-tool") {
61049
61199
  return raw;
@@ -61373,7 +61523,11 @@ async function runAgentAdditionalMessageModeCli(args) {
61373
61523
  async function runAgentsCli(args) {
61374
61524
  const subcommand = args[0];
61375
61525
  const rest = args.slice(1);
61376
- if (!subcommand || subcommand === "list") {
61526
+ if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
61527
+ console.log(renderAgentsHelp());
61528
+ return;
61529
+ }
61530
+ if (subcommand === "list") {
61377
61531
  await listAgents(rest);
61378
61532
  return;
61379
61533
  }
@@ -61405,7 +61559,7 @@ async function runAgentsCli(args) {
61405
61559
  await unbindAgent(rest);
61406
61560
  return;
61407
61561
  }
61408
- throw new Error(`Unknown agents subcommand: ${subcommand}`);
61562
+ throw new Error(renderAgentsHelp());
61409
61563
  }
61410
61564
 
61411
61565
  // src/control/accounts-cli.ts
@@ -63505,6 +63659,12 @@ function collapseBlankLines(lines) {
63505
63659
  }
63506
63660
  return collapsed;
63507
63661
  }
63662
+ var DURATION_STATUS_PATTERN = String.raw`(?:\d+(?:h|m|s))(?:\s+\d+(?:h|m|s)){0,2}`;
63663
+ var CODEX_WORKING_STATUS_PATTERN = new RegExp(String.raw`^(?:[•◦·]\s*)?Working(?:\s*\()?${DURATION_STATUS_PATTERN}\b.*(?:esc to interrupt|interrupt)\)?$`, "i");
63664
+ var CODEX_INTERRUPT_FOOTER_PATTERN = new RegExp(String.raw`^(?:[•◦·]\s*)?${DURATION_STATUS_PATTERN}\s*[•◦·]?\s*esc to interrupt\)?$`, "i");
63665
+ var GEMINI_THINKING_STATUS_PATTERN = new RegExp(String.raw`^Thinking\.\.\. \(esc to cancel,\s*${DURATION_STATUS_PATTERN}\)$`, "i");
63666
+ var CLAUDE_WORKED_STATUS_PATTERN = new RegExp(String.raw`^(?:[✻✽*]\s*)?(?:Worked|Cooked) for ${DURATION_STATUS_PATTERN}$`, "i");
63667
+ var CLAUDE_TIMER_FOOTER_PATTERN = new RegExp(String.raw`\|\s*claude\s*\|.*\|\s*${DURATION_STATUS_PATTERN}\s*$`, "i");
63508
63668
  function looksLikeUrlContinuation(line) {
63509
63669
  const trimmed = line.trim();
63510
63670
  return trimmed.startsWith("(") || trimmed.startsWith("http://") || trimmed.startsWith("https://");
@@ -63702,7 +63862,14 @@ function isInterruptStatusLine(line) {
63702
63862
  if (!trimmed) {
63703
63863
  return false;
63704
63864
  }
63705
- return /^(?:[•◦·]\s*)?Working(?:\s*\()?\d+s\b.*(?:esc to interrupt|interrupt)\)?$/i.test(trimmed) || /^(?:[•◦·]\s*)?\d+s\s*[•◦·]?\s*esc to interrupt\)?$/i.test(trimmed);
63865
+ return CODEX_WORKING_STATUS_PATTERN.test(trimmed) || CODEX_INTERRUPT_FOOTER_PATTERN.test(trimmed);
63866
+ }
63867
+ function isTimerDrivenStatusLine(line) {
63868
+ const trimmed = line.trim();
63869
+ if (!trimmed) {
63870
+ return false;
63871
+ }
63872
+ return isInterruptStatusLine(trimmed) || GEMINI_THINKING_STATUS_PATTERN.test(trimmed) || CLAUDE_WORKED_STATUS_PATTERN.test(trimmed) || CLAUDE_TIMER_FOOTER_PATTERN.test(trimmed);
63706
63873
  }
63707
63874
  function shouldDropCodexChromeLine(line) {
63708
63875
  const trimmed = line.trim();
@@ -63716,14 +63883,14 @@ function shouldDropClaudeChromeLine(line) {
63716
63883
  if (!trimmed) {
63717
63884
  return false;
63718
63885
  }
63719
- return trimmed.includes("Claude Code v") || trimmed.includes("Welcome back!") || trimmed.includes("Tips for getting started") || trimmed.includes("Ask Claude to create a new app or clone a repository") || trimmed.includes("Recent activity") || trimmed.includes("No recent activity") || trimmed.includes("API Usage Billing") || trimmed.includes("shift+tab to cycle") || trimmed.includes("ctrl+o to expand") || trimmed.includes("ctrl+b ctrl+b") || trimmed.includes("run in background") || /^~\/\.clisbot\/(?:workspace\/)?[a-z0-9._/-]+$/i.test(trimmed) || trimmed.includes("| claude |") || /^(?:[✻*]\s*)?(?:Worked|Cooked) for \d+s$/i.test(trimmed) || trimmed.startsWith("⏵⏵") || trimmed.startsWith("❯") || isProgressLine(trimmed) || /^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(trimmed) || /^[╭╰│]/.test(trimmed) || /^─+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+.+$/.test(trimmed);
63886
+ return trimmed.includes("Claude Code v") || trimmed.includes("Welcome back!") || trimmed.includes("Tips for getting started") || trimmed.includes("Ask Claude to create a new app or clone a repository") || trimmed.includes("Recent activity") || trimmed.includes("No recent activity") || trimmed.includes("API Usage Billing") || trimmed.includes("shift+tab to cycle") || trimmed.includes("ctrl+o to expand") || trimmed.includes("ctrl+b ctrl+b") || trimmed.includes("run in background") || /^~\/\.clisbot\/(?:workspace\/)?[a-z0-9._/-]+$/i.test(trimmed) || trimmed.includes("| claude |") || isTimerDrivenStatusLine(trimmed) || trimmed.startsWith("⏵⏵") || trimmed.startsWith("❯") || isProgressLine(trimmed) || /^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(trimmed) || /^[╭╰│]/.test(trimmed) || /^─+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+.+$/.test(trimmed);
63720
63887
  }
63721
63888
  function shouldDropGeminiChromeLine(line) {
63722
63889
  const trimmed = line.trim();
63723
63890
  if (!trimmed) {
63724
63891
  return false;
63725
63892
  }
63726
- return trimmed.includes("Gemini CLI v") || trimmed.includes("Signed in with Google") || trimmed.includes("Plan:") || /^[▝▜▄▗▟▀ ]+$/.test(trimmed) || trimmed.includes("We're making changes to Gemini CLI") || trimmed.includes("What's Changing:") || trimmed.includes("How it affects you:") || trimmed.includes("Read more: https://goo.gle/geminicli-updates") || trimmed.includes("Skipping project agents due to untrusted folder.") || trimmed.includes("Do you trust the files in this folder?") || trimmed.includes("Trusting a folder allows Gemini CLI to load its local configurations") || trimmed === "1. Trust folder (default)" || trimmed === "2. Trust parent folder (workspaces)" || trimmed === "3. Don't trust" || trimmed.includes("Tips for getting started") || /^Create GEMINI\.md files to customize your interactions$/i.test(trimmed) || /^\/help for more information$/i.test(trimmed) || /^Ask coding questions, edit code or run commands$/i.test(trimmed) || /^Be specific for the best results$/i.test(trimmed) || trimmed.includes("? for shortcuts") || trimmed.includes("YOLO Ctrl+Y") || trimmed.includes("Type your message or @path/to/file") || trimmed.includes("workspace (/directory)") || /^~\/.+\s+\S+\s+no sandbox\s+\S+/i.test(trimmed) || /^Thinking\.\.\. \(esc to cancel,\s*\d+s\)$/i.test(trimmed) || /^[╭╰│]/.test(trimmed) || /^[-▀▄]{10,}$/.test(trimmed) || /^─+$/.test(trimmed);
63893
+ return trimmed.includes("Gemini CLI v") || trimmed.includes("Signed in with Google") || trimmed.includes("Plan:") || /^[▝▜▄▗▟▀ ]+$/.test(trimmed) || trimmed.includes("We're making changes to Gemini CLI") || trimmed.includes("What's Changing:") || trimmed.includes("How it affects you:") || trimmed.includes("Read more: https://goo.gle/geminicli-updates") || trimmed.includes("Skipping project agents due to untrusted folder.") || trimmed.includes("Do you trust the files in this folder?") || trimmed.includes("Trusting a folder allows Gemini CLI to load its local configurations") || trimmed === "1. Trust folder (default)" || trimmed === "2. Trust parent folder (workspaces)" || trimmed === "3. Don't trust" || trimmed.includes("Tips for getting started") || /^Create GEMINI\.md files to customize your interactions$/i.test(trimmed) || /^\/help for more information$/i.test(trimmed) || /^Ask coding questions, edit code or run commands$/i.test(trimmed) || /^Be specific for the best results$/i.test(trimmed) || trimmed.includes("? for shortcuts") || trimmed.includes("YOLO Ctrl+Y") || trimmed.includes("Type your message or @path/to/file") || trimmed.includes("workspace (/directory)") || /^~\/.+\s+\S+\s+no sandbox\s+\S+/i.test(trimmed) || isTimerDrivenStatusLine(trimmed) || /^[╭╰│]/.test(trimmed) || /^[-▀▄]{10,}$/.test(trimmed) || /^─+$/.test(trimmed);
63727
63894
  }
63728
63895
  function normalizeBoundaryLine(line) {
63729
63896
  return line.trim().replace(/^(?::eight_spoked_asterisk:|[-*•◦·✽✶])\s+/, "");
@@ -63757,16 +63924,21 @@ function collapseAdjacentDuplicateLines(raw) {
63757
63924
  return collapseBlankLines(collapsed).join(`
63758
63925
  `).trim();
63759
63926
  }
63760
- function cleanInteractionSnapshot(raw) {
63927
+ function cleanInteractionSnapshotInternal(raw, options) {
63761
63928
  const lines = splitNormalizedLines(raw);
63762
63929
  const isCodex = looksLikeCodexSnapshot(lines);
63763
63930
  const isClaude = looksLikeClaudeSnapshot(lines);
63764
63931
  const isGemini = looksLikeGeminiSnapshot(lines);
63765
63932
  const promptStripped = isCodex ? dropCodexPromptBlocks(lines) : isClaude ? dropClaudePromptBlocks(lines) : isGemini ? dropGeminiPromptBlocks(lines) : lines;
63933
+ const timerStatusLines = [];
63766
63934
  const filtered = promptStripped.filter((line) => {
63767
63935
  if (shouldDropDeliveryReportLine(line)) {
63768
63936
  return false;
63769
63937
  }
63938
+ if (options?.preserveTimerStatusLines && isTimerDrivenStatusLine(line)) {
63939
+ timerStatusLines.push(line.trim());
63940
+ return false;
63941
+ }
63770
63942
  if (isCodex && shouldDropCodexChromeLine(line)) {
63771
63943
  return false;
63772
63944
  }
@@ -63780,8 +63952,21 @@ function cleanInteractionSnapshot(raw) {
63780
63952
  });
63781
63953
  const normalized = isCodex ? unwrapCodexMessageBlocks(filtered) : isClaude ? unwrapClaudeMessageBlocks(filtered) : isGemini ? filtered.map((line) => line.replace(/^\s*>\s*/, "")) : filtered;
63782
63954
  const unwrapped = unwrapSoftWrappedLines(normalized);
63783
- return collapseAdjacentDuplicateLines(collapseBlankLines(trimBlankLines(unwrapped)).join(`
63955
+ const cleanedBody = collapseAdjacentDuplicateLines(collapseBlankLines(trimBlankLines(unwrapped)).join(`
63784
63956
  `));
63957
+ const cleanedTimerStatus = options?.preserveTimerStatusLines ? collapseAdjacentDuplicateLines(collapseBlankLines(trimBlankLines(timerStatusLines)).join(`
63958
+ `)) : "";
63959
+ return [cleanedBody, cleanedTimerStatus].filter(Boolean).join(`
63960
+
63961
+ `).trim();
63962
+ }
63963
+ function cleanInteractionSnapshot(raw) {
63964
+ return cleanInteractionSnapshotInternal(raw);
63965
+ }
63966
+ function cleanRunningInteractionSnapshot(raw) {
63967
+ return cleanInteractionSnapshotInternal(raw, {
63968
+ preserveTimerStatusLines: true
63969
+ });
63785
63970
  }
63786
63971
  // src/shared/transcript-delta.ts
63787
63972
  function diffText(previous, current) {
@@ -63842,16 +64027,8 @@ function extractScrolledAppend(previous, current) {
63842
64027
  }
63843
64028
  return "";
63844
64029
  }
63845
- function deriveRunningInteractionText(previousSnapshot, currentSnapshot) {
63846
- const previous = cleanInteractionSnapshot(previousSnapshot);
63847
- const current = cleanInteractionSnapshot(currentSnapshot);
63848
- if (!current || current === previous) {
63849
- return "";
63850
- }
63851
- if (!previous) {
63852
- return current;
63853
- }
63854
- return extractScrolledAppend(previous, current);
64030
+ function deriveRunningInteractionSnapshot(currentSnapshot) {
64031
+ return cleanRunningInteractionSnapshot(currentSnapshot);
63855
64032
  }
63856
64033
  function deriveInteractionText(initialSnapshot, currentSnapshot) {
63857
64034
  const previous = cleanInteractionSnapshot(initialSnapshot);
@@ -64593,6 +64770,7 @@ var TMUX_DUPLICATE_SESSION_PATTERN = /duplicate session:/i;
64593
64770
  var TMUX_TRANSIENT_TARGET_PATTERN = /(?:no current target|can't find pane|can't find window)/i;
64594
64771
  var SESSION_READY_CAPTURE_RETRY_COUNT = 5;
64595
64772
  var SESSION_READY_CAPTURE_RETRY_DELAY_MS = 100;
64773
+ var SESSION_ID_CAPTURE_FAILURE_COOLDOWN_MS = 15000;
64596
64774
  function summarizeSnapshot(snapshot) {
64597
64775
  const compact = snapshot.split(`
64598
64776
  `).map((line) => line.trim()).filter(Boolean).join(" ").slice(0, 220);
@@ -64624,6 +64802,7 @@ class RunnerService {
64624
64802
  sessionState;
64625
64803
  resolveTarget;
64626
64804
  cleanupInFlight = false;
64805
+ sessionIdentityCaptureRetryAt = new Map;
64627
64806
  constructor(loadedConfig, tmux, sessionState, resolveTarget) {
64628
64807
  this.loadedConfig = loadedConfig;
64629
64808
  this.tmux = tmux;
@@ -64675,12 +64854,24 @@ class RunnerService {
64675
64854
  async syncSessionIdentity(resolved) {
64676
64855
  const existing = await this.sessionState.getEntry(resolved.sessionKey);
64677
64856
  if (existing?.sessionId) {
64857
+ this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
64678
64858
  return this.sessionState.touchSessionEntry(resolved, {
64679
64859
  sessionId: existing.sessionId,
64680
64860
  runnerCommand: resolved.runner.command
64681
64861
  });
64682
64862
  }
64863
+ const retryAt = this.sessionIdentityCaptureRetryAt.get(resolved.sessionKey) ?? 0;
64864
+ if (retryAt > Date.now()) {
64865
+ return this.sessionState.touchSessionEntry(resolved, {
64866
+ runnerCommand: resolved.runner.command
64867
+ });
64868
+ }
64683
64869
  const sessionId = await this.captureSessionIdentity(resolved);
64870
+ if (sessionId) {
64871
+ this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
64872
+ } else {
64873
+ this.sessionIdentityCaptureRetryAt.set(resolved.sessionKey, Date.now() + SESSION_ID_CAPTURE_FAILURE_COOLDOWN_MS);
64874
+ }
64684
64875
  return this.sessionState.touchSessionEntry(resolved, {
64685
64876
  sessionId,
64686
64877
  runnerCommand: resolved.runner.command
@@ -64835,6 +65026,7 @@ class RunnerService {
64835
65026
  sessionName: resolved.sessionName,
64836
65027
  stateDir: this.loadedConfig.stateDir
64837
65028
  });
65029
+ this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
64838
65030
  try {
64839
65031
  await this.tmux.newSession({
64840
65032
  sessionName: resolved.sessionName,
@@ -65101,11 +65293,12 @@ function buildRunRecoveryNote(kind, params) {
65101
65293
  var FIRST_OUTPUT_POLL_INTERVAL_MS = 250;
65102
65294
  async function monitorTmuxRun(params) {
65103
65295
  let previousSnapshot = params.initialSnapshot;
65104
- let lastChangeAt = Date.now();
65105
- let sawChange = false;
65106
- let cumulativeInteractionSnapshot = "";
65296
+ let previousRunningSnapshot = "";
65297
+ let lastActivityAt = params.startedAt;
65298
+ let sawActivity = false;
65107
65299
  let detachedNotified = params.detachedAlready;
65108
65300
  let firstMeaningfulDeltaLogged = false;
65301
+ let noOutputThresholdLogged = false;
65109
65302
  if (params.prompt) {
65110
65303
  logLatencyDebug("tmux-submit-start", params.timingContext, {
65111
65304
  sessionName: params.sessionName,
@@ -65126,61 +65319,57 @@ async function monitorTmuxRun(params) {
65126
65319
  });
65127
65320
  }
65128
65321
  while (true) {
65129
- await sleep(sawChange ? params.updateIntervalMs : Math.min(params.updateIntervalMs, FIRST_OUTPUT_POLL_INTERVAL_MS));
65322
+ await sleep(sawActivity ? params.updateIntervalMs : Math.min(params.updateIntervalMs, FIRST_OUTPUT_POLL_INTERVAL_MS));
65130
65323
  const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
65131
65324
  const now = Date.now();
65132
- if (snapshot !== previousSnapshot) {
65133
- const priorSnapshot = previousSnapshot;
65134
- lastChangeAt = now;
65135
- previousSnapshot = snapshot;
65136
- const interactionDelta = deriveRunningInteractionText(priorSnapshot, snapshot);
65137
- const nextInteractionSnapshot = appendInteractionText(cumulativeInteractionSnapshot, interactionDelta);
65138
- if (nextInteractionSnapshot && nextInteractionSnapshot !== cumulativeInteractionSnapshot) {
65139
- sawChange = true;
65140
- cumulativeInteractionSnapshot = nextInteractionSnapshot;
65141
- if (!firstMeaningfulDeltaLogged) {
65142
- firstMeaningfulDeltaLogged = true;
65143
- logLatencyDebug("tmux-first-meaningful-delta", params.timingContext, {
65144
- sessionName: params.sessionName,
65145
- elapsedMs: now - params.startedAt
65146
- });
65147
- }
65148
- await params.onRunning({
65149
- snapshot: cumulativeInteractionSnapshot,
65150
- fullSnapshot: snapshot,
65151
- initialSnapshot: params.initialSnapshot
65325
+ const runningSnapshot = deriveRunningInteractionSnapshot(snapshot);
65326
+ previousSnapshot = snapshot;
65327
+ if (runningSnapshot && runningSnapshot !== previousRunningSnapshot) {
65328
+ previousRunningSnapshot = runningSnapshot;
65329
+ lastActivityAt = now;
65330
+ sawActivity = true;
65331
+ if (!firstMeaningfulDeltaLogged) {
65332
+ firstMeaningfulDeltaLogged = true;
65333
+ logLatencyDebug("tmux-first-meaningful-delta", params.timingContext, {
65334
+ sessionName: params.sessionName,
65335
+ elapsedMs: now - params.startedAt
65152
65336
  });
65153
65337
  }
65338
+ await params.onRunning({
65339
+ snapshot: runningSnapshot,
65340
+ fullSnapshot: snapshot,
65341
+ initialSnapshot: params.initialSnapshot
65342
+ });
65154
65343
  }
65155
65344
  if (!detachedNotified && now - params.startedAt >= params.maxRuntimeMs) {
65156
65345
  detachedNotified = true;
65157
65346
  await params.onDetached({
65158
- snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
65347
+ snapshot: previousRunningSnapshot || deriveInteractionText(params.initialSnapshot, snapshot),
65159
65348
  fullSnapshot: previousSnapshot,
65160
65349
  initialSnapshot: params.initialSnapshot
65161
65350
  });
65162
65351
  }
65163
- if (sawChange && now - lastChangeAt >= params.idleTimeoutMs) {
65352
+ if (sawActivity && now - lastActivityAt >= params.idleTimeoutMs) {
65164
65353
  await params.onCompleted({
65165
- snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
65354
+ snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
65166
65355
  fullSnapshot: previousSnapshot,
65167
65356
  initialSnapshot: params.initialSnapshot
65168
65357
  });
65169
65358
  return;
65170
65359
  }
65171
- if (!sawChange && now - params.startedAt >= params.noOutputTimeoutMs) {
65172
- await params.onTimeout({
65173
- snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
65174
- fullSnapshot: previousSnapshot,
65175
- initialSnapshot: params.initialSnapshot
65360
+ if (!noOutputThresholdLogged && !sawActivity && now - params.startedAt >= params.noOutputTimeoutMs) {
65361
+ noOutputThresholdLogged = true;
65362
+ logLatencyDebug("tmux-no-output-threshold-crossed", params.timingContext, {
65363
+ sessionName: params.sessionName,
65364
+ elapsedMs: now - params.startedAt
65176
65365
  });
65177
- return;
65178
65366
  }
65179
65367
  }
65180
65368
  }
65181
65369
 
65182
65370
  // src/agents/session-service.ts
65183
65371
  var OBSERVER_RETRYABLE_FAILURE_LIMIT = 3;
65372
+ var DETACHED_OBSERVER_INTERVAL_MS = 5 * 60000;
65184
65373
  function formatObserverError(error) {
65185
65374
  return error instanceof Error ? error.stack ?? error.message : String(error);
65186
65375
  }
@@ -65428,7 +65617,10 @@ class SessionService {
65428
65617
  detached: false
65429
65618
  };
65430
65619
  }
65431
- observer.mode = "passive-final";
65620
+ observer.mode = "poll";
65621
+ observer.intervalMs = DETACHED_OBSERVER_INTERVAL_MS;
65622
+ observer.expiresAt = undefined;
65623
+ observer.lastSentAt = Date.now();
65432
65624
  run.observerFailures.delete(observerId);
65433
65625
  return {
65434
65626
  detached: true
@@ -65451,7 +65643,19 @@ class SessionService {
65451
65643
  this.activeRuns.clear();
65452
65644
  }
65453
65645
  buildDetachedNote(resolved) {
65454
- return `This session has been running for over ${resolved.stream.maxRuntimeLabel}. clisbot will keep monitoring it and will post the final result here when it completes. Use \`/attach\` to resume live updates, \`/watch every 30s\` for interval updates, or \`/stop\` to interrupt it.`;
65646
+ return `This session has been running for over ${resolved.stream.maxRuntimeLabel}. clisbot will keep monitoring it, switch this thread to sparse progress updates, and post the final result here when it completes. Use \`/attach\` to resume live updates, \`/watch every 30s\` for interval updates, or \`/stop\` to interrupt it.`;
65647
+ }
65648
+ applyDetachedObserverPolicy(run) {
65649
+ const now = Date.now();
65650
+ for (const observer of run.observers.values()) {
65651
+ if (observer.mode !== "live") {
65652
+ continue;
65653
+ }
65654
+ observer.mode = "poll";
65655
+ observer.intervalMs = DETACHED_OBSERVER_INTERVAL_MS;
65656
+ observer.expiresAt = undefined;
65657
+ observer.lastSentAt = now;
65658
+ }
65455
65659
  }
65456
65660
  createRunUpdate(params) {
65457
65661
  return {
@@ -65682,7 +65886,8 @@ class SessionService {
65682
65886
  startedAt: params.startedAt,
65683
65887
  detachedAt: Date.now()
65684
65888
  });
65685
- currentRun.latestUpdate = detachedUpdate;
65889
+ await this.notifyRunObservers(currentRun, detachedUpdate);
65890
+ this.applyDetachedObserverPolicy(currentRun);
65686
65891
  currentRun.initialResult.resolve(detachedUpdate);
65687
65892
  },
65688
65893
  onCompleted: async (update) => {
@@ -65694,16 +65899,6 @@ class SessionService {
65694
65899
  initialSnapshot: update.initialSnapshot
65695
65900
  });
65696
65901
  await this.finishActiveRun(sessionKey, runUpdate);
65697
- },
65698
- onTimeout: async (update) => {
65699
- const runUpdate = this.createRunUpdate({
65700
- resolved: run.resolved,
65701
- status: "timeout",
65702
- snapshot: mergeRunSnapshot(params.snapshotPrefix ?? "", update.snapshot),
65703
- fullSnapshot: update.fullSnapshot,
65704
- initialSnapshot: update.initialSnapshot
65705
- });
65706
- await this.finishActiveRun(sessionKey, runUpdate);
65707
65902
  }
65708
65903
  });
65709
65904
  } catch (error) {
@@ -65720,6 +65915,28 @@ class SessionService {
65720
65915
  }
65721
65916
 
65722
65917
  // src/agents/agent-service.ts
65918
+ function shellQuote3(value) {
65919
+ if (/^[a-zA-Z0-9_./:@=-]+$/.test(value)) {
65920
+ return value;
65921
+ }
65922
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
65923
+ }
65924
+ function buildCommandString2(command, args) {
65925
+ return [command, ...args].map(shellQuote3).join(" ");
65926
+ }
65927
+ function stripWorkspaceArgs(args) {
65928
+ const filtered = [];
65929
+ for (let index = 0;index < args.length; index += 1) {
65930
+ const current = args[index];
65931
+ if (current === "-C") {
65932
+ index += 1;
65933
+ continue;
65934
+ }
65935
+ filtered.push(current);
65936
+ }
65937
+ return filtered;
65938
+ }
65939
+
65723
65940
  class AgentService {
65724
65941
  loadedConfig;
65725
65942
  tmuxClient;
@@ -65810,6 +66027,15 @@ class AgentService {
65810
66027
  async getSessionRuntime(target) {
65811
66028
  return this.sessionState.getSessionRuntime(target);
65812
66029
  }
66030
+ async getSessionDiagnostics(target) {
66031
+ const resolved = this.resolveTarget(target);
66032
+ const entry = await this.sessionState.getEntry(target.sessionKey);
66033
+ const sessionId = entry?.sessionId?.trim() || undefined;
66034
+ return {
66035
+ sessionId,
66036
+ resumeCommand: this.buildResumeCommandPreview(resolved, sessionId)
66037
+ };
66038
+ }
65813
66039
  async listActiveSessionRuntimes() {
65814
66040
  return this.sessionState.listActiveSessionRuntimes();
65815
66041
  }
@@ -66052,6 +66278,21 @@ class AgentService {
66052
66278
  this.scheduleIntervalLoopTimer(persisted.id, Math.max(0, persisted.nextRunAt - Date.now()));
66053
66279
  }
66054
66280
  }
66281
+ buildResumeCommandPreview(resolved, sessionId) {
66282
+ if (!sessionId || resolved.runner.sessionId.resume.mode !== "command") {
66283
+ return;
66284
+ }
66285
+ const values = {
66286
+ agentId: resolved.agentId,
66287
+ workspace: resolved.workspacePath,
66288
+ sessionName: resolved.sessionName,
66289
+ sessionKey: resolved.sessionKey,
66290
+ sessionId
66291
+ };
66292
+ const command = resolved.runner.sessionId.resume.command ?? resolved.runner.command;
66293
+ const args = stripWorkspaceArgs(resolved.runner.sessionId.resume.args.map((value) => applyTemplate(value, values)));
66294
+ return buildCommandString2(command, args);
66295
+ }
66055
66296
  async isManagedLoopPersisted(managed) {
66056
66297
  const entry = await this.sessionState.getEntry(managed.target.sessionKey);
66057
66298
  return (entry?.intervalLoops ?? []).some((loop) => loop.id === managed.loop.id);
@@ -66556,6 +66797,12 @@ function parseAgentCommand(text, options = {}) {
66556
66797
  if (lowered === "loop") {
66557
66798
  const loopText = withoutSlash.slice(command.length).trim();
66558
66799
  const loweredLoopText = loopText.toLowerCase();
66800
+ if (!loweredLoopText || loweredLoopText === "help") {
66801
+ return {
66802
+ type: "control",
66803
+ name: "loop-help"
66804
+ };
66805
+ }
66559
66806
  if (loweredLoopText === "status") {
66560
66807
  return {
66561
66808
  type: "loop-control",
@@ -66603,6 +66850,12 @@ function parseAgentCommand(text, options = {}) {
66603
66850
  const queueText = withoutSlash.slice(command.length).trim();
66604
66851
  const normalizedQueueText = queueText.toLowerCase();
66605
66852
  if (lowered === "queue") {
66853
+ if (normalizedQueueText === "help") {
66854
+ return {
66855
+ type: "control",
66856
+ name: "queue-help"
66857
+ };
66858
+ }
66606
66859
  if (normalizedQueueText === "list") {
66607
66860
  return {
66608
66861
  type: "control",
@@ -66669,7 +66922,7 @@ function renderAgentControlSlashHelp() {
66669
66922
  "- `/whoami`: show the current platform, route, and sender identity details",
66670
66923
  "- `/transcript`: show the current conversation session transcript when the route verbose policy allows it",
66671
66924
  "- `/attach`: attach this thread to the active run and resume live updates when it is still processing",
66672
- "- `/detach`: stop live updates for this thread while still allowing final settlement here",
66925
+ "- `/detach`: stop live updates for this thread, switch to sparse progress updates, and still allow final settlement here",
66673
66926
  "- `/watch every 30s [for 10m]`: post the latest state on an interval until the run settles or the watch window ends",
66674
66927
  "- `/stop`: send Escape to interrupt the current conversation session",
66675
66928
  "- `/nudge`: send one extra Enter to the current tmux session without resending the prompt text",
@@ -66686,9 +66939,11 @@ function renderAgentControlSlashHelp() {
66686
66939
  "- `/additionalmessagemode steer`: send later user messages straight into the active session",
66687
66940
  "- `/additionalmessagemode queue`: queue later user messages behind the active run for this surface",
66688
66941
  "- `/queue <message>` or `\\q <message>`: enqueue a later message behind the active run and let clisbot deliver it in order",
66942
+ "- `/queue help`: show queue-specific help and examples",
66689
66943
  "- `/steer <message>` or `\\s <message>`: inject a steering message into the active run immediately",
66690
66944
  "- `/queue list`: show queued messages that have not started yet",
66691
66945
  "- `/queue clear`: clear queued messages that have not started yet",
66946
+ "- `/loop help`: show loop-specific help and syntax examples",
66692
66947
  ...renderLoopHelpLines(),
66693
66948
  "- `/bash` followed by a shell command: requires `shellExecute` on the resolved agent role",
66694
66949
  "- shortcut prefixes such as `!` run bash only when the resolved agent role allows `shellExecute`",
@@ -66697,6 +66952,15 @@ function renderAgentControlSlashHelp() {
66697
66952
  ].join(`
66698
66953
  `);
66699
66954
  }
66955
+ function renderQueueHelpLines() {
66956
+ return [
66957
+ "- `/queue <message>` or `\\q <message>`: enqueue one later message behind the active run",
66958
+ "- `/queue list`: show queued messages that have not started yet",
66959
+ "- `/queue clear`: clear queued messages that have not started yet",
66960
+ "- `/queue help`: show this queue help again",
66961
+ "- `/steer <message>` or `\\s <message>`: inject an immediate steering message instead of queueing"
66962
+ ];
66963
+ }
66700
66964
  function parseWatchCommand(raw) {
66701
66965
  const match = raw.match(/^every\s+(\S+)(?:\s+for\s+(\S+))?$/i);
66702
66966
  if (!match) {
@@ -66918,7 +67182,8 @@ function renderWhoAmIMessage(params) {
66918
67182
  `platform: \`${params.identity.platform}\``,
66919
67183
  `conversationKind: \`${params.identity.conversationKind}\``,
66920
67184
  `agentId: \`${params.route.agentId}\``,
66921
- `sessionKey: \`${params.sessionTarget.sessionKey}\``
67185
+ `sessionKey: \`${params.sessionTarget.sessionKey}\``,
67186
+ `storedSessionId: \`${params.sessionDiagnostics.sessionId ?? "(not captured yet)"}\``
66922
67187
  ];
66923
67188
  if (params.identity.senderId) {
66924
67189
  lines.push(`senderId: \`${params.identity.senderId}\``);
@@ -66935,7 +67200,7 @@ function renderWhoAmIMessage(params) {
66935
67200
  if (params.identity.topicId) {
66936
67201
  lines.push(`topicId: \`${params.identity.topicId}\``);
66937
67202
  }
66938
- lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
67203
+ lines.push(`resumeCommand: \`${params.sessionDiagnostics.resumeCommand ?? "(not available yet)"}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
66939
67204
  return lines.join(`
66940
67205
  `);
66941
67206
  }
@@ -66946,7 +67211,8 @@ function renderRouteStatusMessage(params) {
66946
67211
  `platform: \`${params.identity.platform}\``,
66947
67212
  `conversationKind: \`${params.identity.conversationKind}\``,
66948
67213
  `agentId: \`${params.route.agentId}\``,
66949
- `sessionKey: \`${params.sessionTarget.sessionKey}\``
67214
+ `sessionKey: \`${params.sessionTarget.sessionKey}\``,
67215
+ `storedSessionId: \`${params.sessionDiagnostics.sessionId ?? "(not captured yet)"}\``
66950
67216
  ];
66951
67217
  if (params.identity.senderId) {
66952
67218
  lines.push(`senderId: \`${params.identity.senderId}\``);
@@ -66963,7 +67229,7 @@ function renderRouteStatusMessage(params) {
66963
67229
  if (params.identity.topicId) {
66964
67230
  lines.push(`topicId: \`${params.identity.topicId}\``);
66965
67231
  }
66966
- lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
67232
+ lines.push(`resumeCommand: \`${params.sessionDiagnostics.resumeCommand ?? "(not available yet)"}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
66967
67233
  if (params.runtimeState.startedAt) {
66968
67234
  lines.push(`run.startedAt: \`${new Date(params.runtimeState.startedAt).toISOString()}\``);
66969
67235
  }
@@ -66977,7 +67243,7 @@ function renderRouteStatusMessage(params) {
66977
67243
  lines.push(`- \`${loop.id}\` ${renderLoopStatusSchedule(loop)} remaining \`${loop.remainingRuns}\` nextRunAt \`${new Date(loop.nextRunAt).toISOString()}\``);
66978
67244
  }
66979
67245
  }
66980
- lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status|on|off|latest|all`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue <message>`, `/steer <message>`", "- `/queue list`, `/queue clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
67246
+ lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status|on|off|latest|all`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop help`, `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue help`, `/queue <message>`, `/steer <message>`", "- `/queue list`, `/queue clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
66981
67247
  return lines.join(`
66982
67248
  `);
66983
67249
  }
@@ -67049,9 +67315,22 @@ function renderQueuedMessagesList(items) {
67049
67315
  return lines.join(`
67050
67316
  `).trimEnd();
67051
67317
  }
67318
+ function renderQueueUsage() {
67319
+ return [
67320
+ "Queue commands",
67321
+ "",
67322
+ ...renderQueueHelpLines(),
67323
+ "",
67324
+ "Notes:",
67325
+ "- use queue when the current run should finish first and the next user request can wait in order",
67326
+ "- use steer when the active run should be nudged or redirected immediately"
67327
+ ].join(`
67328
+ `);
67329
+ }
67052
67330
  function renderLoopUsage() {
67053
67331
  return [
67054
67332
  "Usage:",
67333
+ "- `/loop help`",
67055
67334
  "- `/loop 5m check CI`",
67056
67335
  "- `/loop 1m --force check CI`",
67057
67336
  "- `/loop 5m`",
@@ -67602,6 +67881,7 @@ async function processChannelInteraction(params) {
67602
67881
  };
67603
67882
  let replyRecorded = false;
67604
67883
  let renderChain = Promise.resolve();
67884
+ const sessionDiagnostics = await params.agentService.getSessionDiagnostics?.(params.sessionTarget) ?? {};
67605
67885
  async function recordReplyIfNeeded() {
67606
67886
  if (replyRecorded) {
67607
67887
  return;
@@ -67676,6 +67956,7 @@ async function processChannelInteraction(params) {
67676
67956
  route: params.route,
67677
67957
  auth,
67678
67958
  sessionTarget: params.sessionTarget,
67959
+ sessionDiagnostics,
67679
67960
  followUpState,
67680
67961
  runtimeState,
67681
67962
  loopState: {
@@ -67696,7 +67977,8 @@ async function processChannelInteraction(params) {
67696
67977
  identity: params.identity,
67697
67978
  route: params.route,
67698
67979
  auth,
67699
- sessionTarget: params.sessionTarget
67980
+ sessionTarget: params.sessionTarget,
67981
+ sessionDiagnostics
67700
67982
  }));
67701
67983
  await params.agentService.recordConversationReply(params.sessionTarget);
67702
67984
  return interactionResult;
@@ -67853,6 +68135,11 @@ async function processChannelInteraction(params) {
67853
68135
  await params.agentService.recordConversationReply(params.sessionTarget);
67854
68136
  return interactionResult;
67855
68137
  }
68138
+ if (slashCommand.name === "queue-help") {
68139
+ await params.postText(renderQueueUsage());
68140
+ await params.agentService.recordConversationReply(params.sessionTarget);
68141
+ return interactionResult;
68142
+ }
67856
68143
  if (slashCommand.name === "queue-clear") {
67857
68144
  const clearedCount = params.agentService.clearQueuedPrompts?.(params.sessionTarget) ?? 0;
67858
68145
  await params.postText(clearedCount > 0 ? `Cleared ${clearedCount} queued message${clearedCount === 1 ? "" : "s"}.` : "Queue was already empty.");
@@ -67860,6 +68147,11 @@ async function processChannelInteraction(params) {
67860
68147
  return interactionResult;
67861
68148
  }
67862
68149
  }
68150
+ if (slashCommand?.type === "control" && slashCommand.name === "loop-help") {
68151
+ await params.postText(renderLoopUsage());
68152
+ await params.agentService.recordConversationReply(params.sessionTarget);
68153
+ return interactionResult;
68154
+ }
67863
68155
  if (slashCommand?.type === "loop-control") {
67864
68156
  if (slashCommand.action === "status") {
67865
68157
  await params.postText(renderLoopStatusMessage({
@@ -68686,7 +68978,7 @@ async function clearSlackAssistantThreadStatus(client, target) {
68686
68978
 
68687
68979
  // src/channels/processing-indicator.ts
68688
68980
  function shouldResolveIndicatorWait(update) {
68689
- return isTerminalRunStatus(update.status) || update.status === "detached";
68981
+ return isTerminalRunStatus(update.status);
68690
68982
  }
68691
68983
  async function waitForProcessingIndicatorLifecycle(params) {
68692
68984
  if (params.lifecycle !== "active-run") {
@@ -68817,6 +69109,7 @@ class ConversationProcessingIndicatorCoordinator {
68817
69109
  }
68818
69110
 
68819
69111
  // src/channels/slack/processing-decoration.ts
69112
+ var DEFAULT_STATUS_REFRESH_INTERVAL_MS = 2000;
68820
69113
  async function activateSlackProcessingDecoration(params) {
68821
69114
  const [reactionResult, statusResult] = await Promise.allSettled([
68822
69115
  params.addReaction(),
@@ -68838,7 +69131,38 @@ async function activateSlackProcessingDecoration(params) {
68838
69131
  throw statusResult.reason;
68839
69132
  }
68840
69133
  }
69134
+ let statusRefreshTimer;
69135
+ let closed = false;
69136
+ let refreshInFlight = false;
69137
+ let refreshPromise;
69138
+ if (statusApplied) {
69139
+ const refreshIntervalMs = Math.max(0, params.statusRefreshIntervalMs ?? DEFAULT_STATUS_REFRESH_INTERVAL_MS);
69140
+ if (refreshIntervalMs > 0) {
69141
+ statusRefreshTimer = setInterval(() => {
69142
+ if (closed || refreshInFlight) {
69143
+ return;
69144
+ }
69145
+ refreshInFlight = true;
69146
+ refreshPromise = params.setStatus().then(() => {
69147
+ return;
69148
+ }).catch((error) => {
69149
+ if (!closed) {
69150
+ params.onUnexpectedError?.("refresh-status", error);
69151
+ }
69152
+ }).finally(() => {
69153
+ refreshInFlight = false;
69154
+ refreshPromise = undefined;
69155
+ });
69156
+ }, refreshIntervalMs);
69157
+ }
69158
+ }
68841
69159
  return async () => {
69160
+ closed = true;
69161
+ if (statusRefreshTimer) {
69162
+ clearInterval(statusRefreshTimer);
69163
+ statusRefreshTimer = undefined;
69164
+ }
69165
+ await refreshPromise;
68842
69166
  if (reactionApplied) {
68843
69167
  try {
68844
69168
  await params.removeReaction();
@@ -69736,20 +70060,21 @@ class SlackSocketService {
69736
70060
  });
69737
70061
  if (!allowed) {
69738
70062
  if (dmConfig.policy === "pairing") {
69739
- const { code, created } = await upsertChannelPairingRequest({
70063
+ const pairingRequest = await upsertChannelPairingRequest({
69740
70064
  channel: "slack",
69741
70065
  id: directUserId
69742
70066
  });
69743
- if (created && code) {
70067
+ const pairingReply = buildPairingReplyFromRequest({
70068
+ channel: "slack",
70069
+ idLine: `Your Slack user id: ${directUserId}`,
70070
+ pairingRequest
70071
+ });
70072
+ if (pairingReply) {
69744
70073
  try {
69745
70074
  await postSlackText(this.app.client, {
69746
70075
  channel: channelId,
69747
70076
  threadTs: directReplyThreadTs,
69748
- text: buildPairingReply({
69749
- channel: "slack",
69750
- idLine: `Your Slack user id: ${directUserId}`,
69751
- code
69752
- })
70077
+ text: pairingReply
69753
70078
  });
69754
70079
  } catch (error) {
69755
70080
  console.error("slack pairing reply failed", error);
@@ -71344,7 +71669,7 @@ class TelegramPollingService {
71344
71669
  });
71345
71670
  if (!allowed) {
71346
71671
  if (directMessages.policy === "pairing") {
71347
- const { code, created } = await upsertChannelPairingRequest({
71672
+ const pairingRequest = await upsertChannelPairingRequest({
71348
71673
  channel: "telegram",
71349
71674
  id: senderId,
71350
71675
  meta: {
@@ -71353,15 +71678,16 @@ class TelegramPollingService {
71353
71678
  lastName: message.from?.last_name
71354
71679
  }
71355
71680
  });
71356
- if (created && code) {
71681
+ const pairingReply = buildPairingReplyFromRequest({
71682
+ channel: "telegram",
71683
+ idLine: `Your Telegram user id: ${senderId}`,
71684
+ pairingRequest
71685
+ });
71686
+ if (pairingReply) {
71357
71687
  try {
71358
71688
  await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
71359
71689
  chat_id: message.chat.id,
71360
- text: buildPairingReply({
71361
- channel: "telegram",
71362
- idLine: `Your Telegram user id: ${senderId}`,
71363
- code
71364
- })
71690
+ text: pairingReply
71365
71691
  });
71366
71692
  } catch (error) {
71367
71693
  console.error("telegram pairing reply failed", error);
@@ -73035,10 +73361,17 @@ function renderAccountsHelp() {
73035
73361
  "",
73036
73362
  "Usage:",
73037
73363
  " clisbot accounts --help",
73364
+ " clisbot accounts help",
73038
73365
  " clisbot accounts add telegram --account <id> --token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
73039
73366
  " clisbot accounts add slack --account <id> --app-token <ENV_NAME|${ENV_NAME}|literal> --bot-token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
73040
73367
  " clisbot accounts persist --channel <slack|telegram> --account <id>",
73041
- " clisbot accounts persist --all"
73368
+ " clisbot accounts persist --all",
73369
+ "",
73370
+ "Notes:",
73371
+ " - env-style input such as `TELEGRAM_BOT_TOKEN` or `${TELEGRAM_BOT_TOKEN}` keeps the account env-backed in config",
73372
+ " - literal token input without `--persist` stays runtime-only and requires a running clisbot runtime",
73373
+ " - `--persist` writes canonical token files so later plain `clisbot start` can reuse the account safely",
73374
+ " - `persist --all` converts every configured `credentialType=mem` account into canonical token files"
73042
73375
  ].join(`
73043
73376
  `);
73044
73377
  }
@@ -73215,7 +73548,7 @@ async function runAccountsCli(args, deps = {}) {
73215
73548
  ...deps
73216
73549
  };
73217
73550
  const action = args[0];
73218
- if (!action || action === "--help" || action === "-h") {
73551
+ if (!action || action === "--help" || action === "-h" || action === "help") {
73219
73552
  console.log(renderAccountsHelp());
73220
73553
  return;
73221
73554
  }
@@ -73319,6 +73652,7 @@ function renderAuthCliHelp() {
73319
73652
  " add-user/remove-user mutate roles.<role>.users",
73320
73653
  " add-permission/remove-permission mutate roles.<role>.allow",
73321
73654
  " agent role edits clone the inherited agent-defaults role into the target agent override on first write",
73655
+ " app `owner` and `admin` principals bypass DM pairing automatically once they are granted",
73322
73656
  "",
73323
73657
  "Examples:",
73324
73658
  " clisbot auth add-user app --role owner --user telegram:1276408333",
@@ -73581,6 +73915,7 @@ function renderChannelsHelp() {
73581
73915
  " - Slack private groups need channels.slack.groups.<groupId>",
73582
73916
  " - Telegram groups need channels.telegram.groups.<chatId>",
73583
73917
  " - Telegram forum topics need channels.telegram.groups.<chatId>.topics.<topicId>",
73918
+ " - route adds for Slack channels, Slack groups, Telegram groups, and Telegram topics default to `requireMention: true` unless you pass `--require-mention false`",
73584
73919
  " - Adding a route puts that surface on the allowlist; other channels, groups, or topics still need to be added explicitly",
73585
73920
  " - Tune route settings such as requireMention and followUp in clisbot.json when a surface should behave differently",
73586
73921
  ` - Manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`,
@@ -73659,6 +73994,7 @@ function renderRouteAddGuidance(params) {
73659
73994
  console.log(` - route added: ${routePath}`);
73660
73995
  console.log(" - direct messages still follow channels.slack.directMessages.policy (`open`, `pairing`, `allowlist`, or `disabled`)");
73661
73996
  console.log(` - this ${routeLabel} still follows channels.slack.groupPolicy and route settings such as requireMention and followUp`);
73997
+ console.log(" - new Slack channel/group routes default to `requireMention: true` unless you passed `--require-mention false`");
73662
73998
  console.log(" - if you want pairing-style access control for DMs, set channels.slack.directMessages.policy to `pairing`");
73663
73999
  console.log(" - if you want stricter route access, keep Slack groups on allowlist and only add the channels/groups you trust");
73664
74000
  console.log(` - manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`);
@@ -73669,6 +74005,7 @@ function renderRouteAddGuidance(params) {
73669
74005
  console.log(` - route added: ${routePath}`);
73670
74006
  console.log(" - direct messages still follow channels.telegram.directMessages.policy (`open`, `pairing`, `allowlist`, or `disabled`)");
73671
74007
  console.log(` - this ${params.kind} is now on the Telegram allowlist; other groups or topics still need to be added explicitly`);
74008
+ console.log(" - new Telegram group/topic routes default to `requireMention: true` unless you passed `--require-mention false`");
73672
74009
  console.log(" - if you want pairing-style access control for DMs, set channels.telegram.directMessages.policy to `pairing`");
73673
74010
  console.log(" - tune route settings such as requireMention and followUp in clisbot.json if this surface should behave differently");
73674
74011
  console.log(` - manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`);
@@ -73867,7 +74204,7 @@ async function addSlackRoute(kind, args) {
73867
74204
  }
73868
74205
  const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
73869
74206
  const agentId = getAgentId(args);
73870
- const requireMention = parseBooleanOption(args, "--require-mention", false);
74207
+ const requireMention = parseBooleanOption(args, "--require-mention", true);
73871
74208
  const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
73872
74209
  target[routeId] = {
73873
74210
  ...target[routeId] ?? {},
@@ -75098,6 +75435,41 @@ function assertSupportedPlatform(command) {
75098
75435
  }
75099
75436
 
75100
75437
  // src/control/runtime-bootstrap-cli.ts
75438
+ function hasHelpFlag(args) {
75439
+ return args.includes("--help") || args.includes("-h") || args.includes("help");
75440
+ }
75441
+ function renderBootstrapCommandHelp(commandName) {
75442
+ const behavior = commandName === "start" ? "seed config if needed and start the detached runtime" : "seed config and optionally bootstrap the first agent without starting runtime";
75443
+ return [
75444
+ `clisbot ${commandName}`,
75445
+ "",
75446
+ "Usage:",
75447
+ ` clisbot ${commandName} --help`,
75448
+ ` clisbot ${commandName} [--cli <codex|claude|gemini>] [--bot-type <personal|team>] [--persist]`,
75449
+ " [--slack-account <id> --slack-app-token <ENV_NAME|${ENV_NAME}|literal> --slack-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
75450
+ " [--telegram-account <id> --telegram-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
75451
+ "",
75452
+ "Behavior:",
75453
+ ` - ${behavior}`,
75454
+ " - first-run agent bootstrap needs both `--cli` and `--bot-type`",
75455
+ " - `--bot-type personal` maps to `personal-assistant`; `--bot-type team` maps to `team-assistant`",
75456
+ " - explicit credential flags only enable the channels and accounts you named in this command",
75457
+ " - env-style values such as `SLACK_APP_TOKEN` or `${SLACK_APP_TOKEN}` stay env-backed in config",
75458
+ commandName === "start" ? " - literal token values without `--persist` stay runtime-only for this start invocation" : " - literal token values on `init` require `--persist` because no runtime exists yet",
75459
+ " - `--persist` writes canonical credential files so later plain `clisbot start` can reuse them",
75460
+ "",
75461
+ "Examples:",
75462
+ ` clisbot ${commandName} --cli codex --bot-type personal --telegram-bot-token TELEGRAM_BOT_TOKEN`,
75463
+ ` clisbot ${commandName} --cli codex --bot-type team --slack-app-token SLACK_APP_TOKEN --slack-bot-token SLACK_BOT_TOKEN`,
75464
+ ` clisbot ${commandName} --cli gemini --bot-type personal --telegram-bot-token "$TELEGRAM_BOT_TOKEN" --persist`,
75465
+ "",
75466
+ "Related help:",
75467
+ " - `clisbot agents --help` for lower-level agent bootstrap and binding control",
75468
+ " - `clisbot accounts --help` for account persistence after first run",
75469
+ " - `clisbot channels --help` for route setup after bootstrap"
75470
+ ].join(`
75471
+ `);
75472
+ }
75101
75473
  function getPrimaryWorkspacePath(summary) {
75102
75474
  const preferredAgentId = summary.channelSummaries.find((channel) => channel.enabled)?.defaultAgentId ?? "default";
75103
75475
  return summary.agentSummaries.find((agent) => agent.id === preferredAgentId)?.workspacePath ?? summary.agentSummaries[0]?.workspacePath;
@@ -75314,6 +75686,10 @@ async function printStartedRuntimeSummary(pid, configPath, logPath) {
75314
75686
  }
75315
75687
  }
75316
75688
  async function initConfig(args = []) {
75689
+ if (hasHelpFlag(args)) {
75690
+ console.log(renderBootstrapCommandHelp("init"));
75691
+ return;
75692
+ }
75317
75693
  const state = await prepareBootstrapState(args, "init");
75318
75694
  if (!state) {
75319
75695
  return;
@@ -75332,6 +75708,10 @@ async function initConfig(args = []) {
75332
75708
  }
75333
75709
  }
75334
75710
  async function start(args = []) {
75711
+ if (hasHelpFlag(args)) {
75712
+ console.log(renderBootstrapCommandHelp("start"));
75713
+ return;
75714
+ }
75335
75715
  const runtimeStatus = await getRuntimeStatus();
75336
75716
  const bootstrapFlags = parseBootstrapFlags(args);
75337
75717
  const restartForLiteralBootstrap = runtimeStatus.running && hasLiteralMemCredentials(bootstrapFlags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clisbot",
3
- "version": "0.1.26",
3
+ "version": "0.1.29",
4
4
  "private": false,
5
5
  "description": "Chat surfaces for durable AI coding agents running in tmux",
6
6
  "license": "MIT",
@@ -90,7 +90,7 @@ These are broad or legacy-looking for the current `clisbot` Slack design and sho
90
90
  | `function_executed` event | Not consumed by current code |
91
91
  | `app_home.messages_tab_enabled` and related App Home surface config | Not required for current chat-routing behavior |
92
92
  | `org_deploy_enabled` | Not needed for the current local or workspace-level setup story |
93
- | `hermes_app_type` and `function_runtime` | Not needed for the current Slack Socket Mode bot |
93
+ | `function_runtime` | Not needed for the current Slack Socket Mode bot |
94
94
 
95
95
  ## Practical Recommendation
96
96
 
@@ -51,7 +51,6 @@
51
51
  },
52
52
  "org_deploy_enabled": false,
53
53
  "socket_mode_enabled": true,
54
- "token_rotation_enabled": false,
55
- "hermes_app_type": "remote"
54
+ "token_rotation_enabled": false
56
55
  }
57
56
  }