clisbot 0.1.45-beta.7 → 0.1.45-beta.9

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/main.js +241 -181
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -139,7 +139,7 @@ Need the step-by-step setup docs instead of the shortest path?
139
139
 
140
140
  - Telegram: [Telegram Bot Setup](docs/user-guide/telegram-setup.md)
141
141
  - Slack: [Slack App Setup](docs/user-guide/slack-setup.md)
142
- - Release history: [CHANGELOG.md](CHANGELOG.md), [release notes](docs/releases/README.md), [update guide](docs/update/README.md), [operator updates](docs/updates/README.md), and [migration index](docs/migrations/index.md)
142
+ - Release history: [CHANGELOG.md](CHANGELOG.md), [release notes](docs/releases/README.md), [update guide](docs/updates/update-guide.md), [release guides](docs/updates/README.md), and [migration index](docs/migrations/index.md)
143
143
  - Slack app manifest template: [app-manifest.json](templates/slack/default/app-manifest.json)
144
144
  - Slack app manifest guide: [app-manifest-guide.md](templates/slack/default/app-manifest-guide.md)
145
145
 
@@ -320,7 +320,7 @@ If the quick start does not work, check these in order:
320
320
  trust_level = "trusted"
321
321
  ```
322
322
 
323
- - If that trust screen is still blocking, attach directly and continue from tmux with `tmux -S ~/.clisbot/state/clisbot.sock attach -t agent-default-main`.
323
+ - If that trust screen is still blocking, inspect the live session name with `clisbot runner list`, then attach directly with `tmux -S ~/.clisbot/state/clisbot.sock attach -t <session-name>`.
324
324
  - If Gemini startup says it is waiting for manual authorization, authenticate Gemini directly first or provide a headless auth path such as `GEMINI_API_KEY` or Vertex AI credentials; `clisbot` now treats that screen as a startup blocker instead of a healthy ready session.
325
325
  - If Codex warns that `bubblewrap` is missing on Linux, install `bubblewrap` in the runtime environment.
326
326
  - If the bot does not answer, check `clisbot status` first. Healthy channels should show `connection=active`; if a channel stays `starting`, inspect `clisbot logs`.
package/dist/main.js CHANGED
@@ -32451,7 +32451,7 @@ var require_websocket = __commonJS((exports, module) => {
32451
32451
  var http = __require("http");
32452
32452
  var net = __require("net");
32453
32453
  var tls = __require("tls");
32454
- var { randomBytes, createHash } = __require("crypto");
32454
+ var { randomBytes, createHash: createHash2 } = __require("crypto");
32455
32455
  var { Duplex, Readable } = __require("stream");
32456
32456
  var { URL: URL2 } = __require("url");
32457
32457
  var PerMessageDeflate = require_permessage_deflate();
@@ -32990,7 +32990,7 @@ var require_websocket = __commonJS((exports, module) => {
32990
32990
  abortHandshake(websocket, socket, "Invalid Upgrade header");
32991
32991
  return;
32992
32992
  }
32993
- const digest = createHash("sha1").update(key + GUID).digest("base64");
32993
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
32994
32994
  if (res.headers["sec-websocket-accept"] !== digest) {
32995
32995
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
32996
32996
  return;
@@ -33363,7 +33363,7 @@ var require_websocket_server = __commonJS((exports, module) => {
33363
33363
  var EventEmitter = __require("events");
33364
33364
  var http = __require("http");
33365
33365
  var { Duplex } = __require("stream");
33366
- var { createHash } = __require("crypto");
33366
+ var { createHash: createHash2 } = __require("crypto");
33367
33367
  var extension = require_extension();
33368
33368
  var PerMessageDeflate = require_permessage_deflate();
33369
33369
  var subprotocol = require_subprotocol();
@@ -33576,7 +33576,7 @@ var require_websocket_server = __commonJS((exports, module) => {
33576
33576
  }
33577
33577
  if (this._state > RUNNING)
33578
33578
  return abortHandshake(socket, 503);
33579
- const digest = createHash("sha1").update(key + GUID).digest("base64");
33579
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
33580
33580
  const headers = [
33581
33581
  "HTTP/1.1 101 Switching Protocols",
33582
33582
  "Upgrade: websocket",
@@ -54803,7 +54803,7 @@ function renderCliHelp() {
54803
54803
  " status Show runtime process, config, log, tmux socket status, and recent runner sessions.",
54804
54804
  " version Show the installed clisbot version.",
54805
54805
  " logs Print the most recent clisbot log lines.",
54806
- " update Print the AI-readable package update guide and release/migration doc links.",
54806
+ " update Print the update guide and release/migration doc links.",
54807
54807
  ` See ${renderCliCommand("update --help", { inline: true })} before asking an agent to update clisbot.`,
54808
54808
  " timezone Manage the app-wide wall-clock timezone used by schedules and loops.",
54809
54809
  ` See ${renderCliCommand("timezone --help", { inline: true })} for override guidance.`,
@@ -65267,14 +65267,20 @@ class TmuxClient {
65267
65267
  }
65268
65268
  return result.stdout;
65269
65269
  }
65270
+ sessionTarget(sessionName) {
65271
+ return `=${sessionName}`;
65272
+ }
65270
65273
  target(sessionName) {
65271
- return `${sessionName}:${MAIN_WINDOW_NAME}`;
65274
+ return `${this.sessionTarget(sessionName)}:${MAIN_WINDOW_NAME}`;
65275
+ }
65276
+ windowTarget(sessionName, windowName) {
65277
+ return `${this.sessionTarget(sessionName)}:${windowName}`;
65272
65278
  }
65273
65279
  rawTarget(target) {
65274
65280
  return target;
65275
65281
  }
65276
65282
  async hasSession(sessionName) {
65277
- const result = await this.exec(["has-session", "-t", sessionName]);
65283
+ const result = await this.exec(["has-session", "-t", this.sessionTarget(sessionName)]);
65278
65284
  return result.exitCode === 0;
65279
65285
  }
65280
65286
  async listSessions() {
@@ -65370,14 +65376,14 @@ ${result.stdout}`.trim();
65370
65376
  "-F",
65371
65377
  "#{pane_id}",
65372
65378
  "-t",
65373
- params.sessionName,
65379
+ this.sessionTarget(params.sessionName),
65374
65380
  "-n",
65375
65381
  params.name,
65376
65382
  "-c",
65377
65383
  params.cwd,
65378
65384
  params.command
65379
65385
  ]);
65380
- await this.freezeWindowName(`${params.sessionName}:${params.name}`);
65386
+ await this.freezeWindowName(this.windowTarget(params.sessionName, params.name));
65381
65387
  return paneId.trim();
65382
65388
  }
65383
65389
  async freezeWindowName(target) {
@@ -65388,7 +65394,7 @@ ${result.stdout}`.trim();
65388
65394
  const output = await this.execOrThrow([
65389
65395
  "list-windows",
65390
65396
  "-t",
65391
- sessionName,
65397
+ this.sessionTarget(sessionName),
65392
65398
  "-F",
65393
65399
  "#{window_name}\t#{pane_id}"
65394
65400
  ]);
@@ -65464,7 +65470,7 @@ ${result.stdout}`.trim();
65464
65470
  };
65465
65471
  }
65466
65472
  async killSession(sessionName) {
65467
- await this.exec(["kill-session", "-t", sessionName]);
65473
+ await this.exec(["kill-session", "-t", this.sessionTarget(sessionName)]);
65468
65474
  }
65469
65475
  async killPane(target) {
65470
65476
  await this.exec(["kill-pane", "-t", this.rawTarget(target)]);
@@ -66541,19 +66547,6 @@ class AgentSessionState {
66541
66547
  agentId: target.agentId
66542
66548
  };
66543
66549
  }
66544
- async listActiveSessionRuntimes() {
66545
- const entries = await this.sessionStore.list();
66546
- return entries.filter(hasActiveRuntime).map((entry) => ({
66547
- state: entry.runtime.state,
66548
- startedAt: entry.runtime.startedAt,
66549
- detachedAt: entry.runtime.detachedAt,
66550
- finalReplyAt: entry.runtime.finalReplyAt,
66551
- lastMessageToolReplyAt: entry.runtime.lastMessageToolReplyAt,
66552
- messageToolFinalReplyAt: entry.runtime.messageToolFinalReplyAt,
66553
- sessionKey: entry.sessionKey,
66554
- agentId: entry.agentId
66555
- }));
66556
- }
66557
66550
  async listIntervalLoops(params) {
66558
66551
  const entries = await this.sessionStore.list();
66559
66552
  return entries.flatMap((entry) => getStoredLoops(entry).filter((loop) => !params?.sessionKey || entry.sessionKey === params.sessionKey).map((loop) => ({
@@ -66811,6 +66804,10 @@ class AgentSessionState {
66811
66804
  const entry = await this.sessionStore.get(sessionKey);
66812
66805
  return getStoredQueues(entry).some((item) => item.status === "pending" || item.status === "running");
66813
66806
  }
66807
+ async hasQueuedItem(sessionKey, queueId) {
66808
+ const entry = await this.sessionStore.get(sessionKey);
66809
+ return getStoredQueues(entry).some((item) => item.id === queueId);
66810
+ }
66814
66811
  async resetStaleRunningQueuedItems(activeSessionKeys) {
66815
66812
  const entries = await this.sessionStore.list();
66816
66813
  let reset = 0;
@@ -66891,11 +66888,9 @@ function getStoredLoops(entry) {
66891
66888
  function getStoredQueues(entry) {
66892
66889
  return entry?.queues ?? [];
66893
66890
  }
66894
- function hasActiveRuntime(entry) {
66895
- return entry.runtime?.state === "running" || entry.runtime?.state === "detached";
66896
- }
66897
66891
 
66898
66892
  // src/agents/session-key.ts
66893
+ import { createHash } from "node:crypto";
66899
66894
  var DEFAULT_MAIN_KEY = "main";
66900
66895
  var DEFAULT_BOT_ID = "default";
66901
66896
  var DEFAULT_ACCOUNT_ID = DEFAULT_BOT_ID;
@@ -66986,7 +66981,8 @@ function buildTmuxSessionName(params) {
66986
66981
  mainKey: normalizeMainKey(params.mainKey)
66987
66982
  });
66988
66983
  const baseName = sanitizeSessionName(rendered);
66989
- return baseName;
66984
+ const sessionHash = createHash("sha1").update(params.sessionKey).digest("hex").slice(0, 8);
66985
+ return `${baseName}-${sessionHash}`;
66990
66986
  }
66991
66987
 
66992
66988
  // src/agents/resolved-target.ts
@@ -67146,39 +67142,15 @@ class AgentJobQueue {
67146
67142
  if (!state) {
67147
67143
  return 0;
67148
67144
  }
67149
- const keptEntries = state.entries.filter((entry) => entry.status === "running");
67150
- const removedEntries = state.entries.filter((entry) => entry.status === "pending");
67151
- state.entries = keptEntries;
67152
- for (const entry of removedEntries) {
67153
- Promise.resolve(entry.lifecycle?.onClear?.()).catch(() => {
67154
- return;
67155
- });
67156
- entry.reject(new ClearedQueuedTaskError);
67157
- }
67158
- if (state.entries.length === 0 && !state.running) {
67159
- this.states.delete(key);
67160
- }
67161
- return removedEntries.length;
67145
+ return this.clearPendingEntriesForState(key, state, () => true);
67162
67146
  }
67163
- clearPendingByIds(ids) {
67164
- const idSet = new Set(ids);
67165
- let cleared = 0;
67166
- for (const [key, state] of this.states.entries()) {
67167
- const keptEntries = state.entries.filter((entry) => entry.status === "running" || !idSet.has(entry.id));
67168
- const removedEntries = state.entries.filter((entry) => entry.status === "pending" && idSet.has(entry.id));
67169
- state.entries = keptEntries;
67170
- cleared += removedEntries.length;
67171
- for (const entry of removedEntries) {
67172
- Promise.resolve(entry.lifecycle?.onClear?.()).catch(() => {
67173
- return;
67174
- });
67175
- entry.reject(new ClearedQueuedTaskError);
67176
- }
67177
- if (state.entries.length === 0 && !state.running) {
67178
- this.states.delete(key);
67179
- }
67147
+ clearPendingByIdsForKey(key, ids) {
67148
+ const state = this.states.get(key);
67149
+ if (!state) {
67150
+ return 0;
67180
67151
  }
67181
- return cleared;
67152
+ const idSet = new Set(ids);
67153
+ return this.clearPendingEntriesForState(key, state, (entry) => idSet.has(entry.id));
67182
67154
  }
67183
67155
  getOrCreateState(key) {
67184
67156
  const existing = this.states.get(key);
@@ -67203,6 +67175,21 @@ class AgentJobQueue {
67203
67175
  return left.sequence - right.sequence;
67204
67176
  });
67205
67177
  }
67178
+ clearPendingEntriesForState(key, state, shouldClear) {
67179
+ const keptEntries = state.entries.filter((entry) => entry.status === "running" || !shouldClear(entry));
67180
+ const removedEntries = state.entries.filter((entry) => entry.status === "pending" && shouldClear(entry));
67181
+ state.entries = keptEntries;
67182
+ for (const entry of removedEntries) {
67183
+ Promise.resolve(entry.lifecycle?.onClear?.()).catch(() => {
67184
+ return;
67185
+ });
67186
+ entry.reject(new ClearedQueuedTaskError);
67187
+ }
67188
+ if (state.entries.length === 0 && !state.running) {
67189
+ this.states.delete(key);
67190
+ }
67191
+ return removedEntries.length;
67192
+ }
67206
67193
  async drain(key, state) {
67207
67194
  if (state.running) {
67208
67195
  return;
@@ -67319,7 +67306,6 @@ function createStoredLoopBase(params) {
67319
67306
  updatedAt: now,
67320
67307
  nextRunAt: params.nextRunAt,
67321
67308
  promptText: params.promptText,
67322
- canonicalPromptText: params.canonicalPromptText,
67323
67309
  protectedControlMutationRule: params.protectedControlMutationRule,
67324
67310
  promptSummary: params.promptSummary,
67325
67311
  promptSource: params.promptSource,
@@ -67350,7 +67336,6 @@ function createStoredIntervalLoop(params) {
67350
67336
  ...createStoredLoopBase({
67351
67337
  nextRunAt: Date.now(),
67352
67338
  promptText: params.promptText,
67353
- canonicalPromptText: params.canonicalPromptText,
67354
67339
  protectedControlMutationRule: params.protectedControlMutationRule,
67355
67340
  promptSummary: params.promptSummary,
67356
67341
  promptSource: params.promptSource,
@@ -67381,7 +67366,6 @@ function createStoredCalendarLoop(params) {
67381
67366
  ...createStoredLoopBase({
67382
67367
  nextRunAt,
67383
67368
  promptText: params.promptText,
67384
- canonicalPromptText: params.canonicalPromptText,
67385
67369
  protectedControlMutationRule: params.protectedControlMutationRule,
67386
67370
  promptSummary: params.promptSummary,
67387
67371
  promptSource: params.promptSource,
@@ -67542,87 +67526,86 @@ class ManagedLoopController {
67542
67526
  }
67543
67527
  async reconcilePersistedIntervalLoops() {
67544
67528
  const persistedLoops = await this.deps.sessionState.listIntervalLoops();
67545
- const persistedIds = new Set;
67529
+ const persistedKeys = new Set;
67546
67530
  for (const persisted of persistedLoops) {
67547
- persistedIds.add(persisted.id);
67531
+ const managedKey = this.buildManagedLoopKey(persisted.sessionKey, persisted.id);
67532
+ persistedKeys.add(managedKey);
67548
67533
  if (persisted.attemptedRuns >= persisted.maxRuns) {
67549
- this.dropManagedIntervalLoop(persisted.id);
67534
+ this.dropManagedIntervalLoop(managedKey);
67550
67535
  continue;
67551
67536
  }
67552
- if (this.intervalLoops.has(persisted.id)) {
67537
+ if (this.intervalLoops.has(managedKey)) {
67553
67538
  continue;
67554
67539
  }
67555
- this.intervalLoops.set(persisted.id, {
67540
+ this.intervalLoops.set(managedKey, {
67556
67541
  target: {
67557
67542
  agentId: persisted.agentId,
67558
67543
  sessionKey: persisted.sessionKey
67559
67544
  },
67560
67545
  loop: persisted
67561
67546
  });
67562
- this.scheduleIntervalLoopTimer(persisted.id, Math.max(0, persisted.nextRunAt - Date.now()));
67547
+ this.scheduleIntervalLoopTimer(managedKey, Math.max(0, persisted.nextRunAt - Date.now()));
67563
67548
  }
67564
- for (const loopId of this.intervalLoops.keys()) {
67565
- if (!persistedIds.has(loopId)) {
67566
- this.dropManagedIntervalLoop(loopId);
67549
+ for (const managedKey of this.intervalLoops.keys()) {
67550
+ if (!persistedKeys.has(managedKey)) {
67551
+ this.dropManagedIntervalLoop(managedKey);
67567
67552
  }
67568
67553
  }
67569
67554
  }
67570
67555
  async createIntervalLoop(params) {
67571
67556
  this.assertActiveLoopCapacity();
67572
67557
  const loop = createStoredIntervalLoop(params);
67558
+ const managedKey = this.buildManagedLoopKey(params.target.sessionKey, loop.id);
67573
67559
  await this.deps.sessionState.setIntervalLoop(this.deps.resolveTarget(params.target), loop);
67574
- this.intervalLoops.set(loop.id, {
67560
+ this.intervalLoops.set(managedKey, {
67575
67561
  target: params.target,
67576
67562
  loop
67577
67563
  });
67578
- await this.runIntervalLoopIteration(loop.id, {
67564
+ await this.runIntervalLoopIteration(managedKey, {
67579
67565
  notifyStart: false
67580
67566
  });
67581
- return this.getIntervalLoop(loop.id);
67567
+ return this.getIntervalLoop(params.target.sessionKey, loop.id);
67582
67568
  }
67583
67569
  async createCalendarLoop(params) {
67584
67570
  this.assertActiveLoopCapacity();
67585
67571
  const loop = createStoredCalendarLoop(params);
67572
+ const managedKey = this.buildManagedLoopKey(params.target.sessionKey, loop.id);
67586
67573
  await this.deps.sessionState.setIntervalLoop(this.deps.resolveTarget(params.target), loop);
67587
- this.intervalLoops.set(loop.id, {
67574
+ this.intervalLoops.set(managedKey, {
67588
67575
  target: params.target,
67589
67576
  loop
67590
67577
  });
67591
- this.scheduleIntervalLoopTimer(loop.id, Math.max(0, loop.nextRunAt - Date.now()));
67592
- return this.getIntervalLoop(loop.id);
67578
+ this.scheduleIntervalLoopTimer(managedKey, Math.max(0, loop.nextRunAt - Date.now()));
67579
+ return this.getIntervalLoop(params.target.sessionKey, loop.id);
67593
67580
  }
67594
- async cancelIntervalLoop(loopId) {
67595
- const managed = this.intervalLoops.get(loopId);
67581
+ async cancelIntervalLoop(target, loopId) {
67582
+ const managedKey = this.buildManagedLoopKey(target.sessionKey, loopId);
67583
+ const managed = this.intervalLoops.get(managedKey);
67596
67584
  if (!managed) {
67597
67585
  return false;
67598
67586
  }
67599
- if (managed.timer) {
67600
- clearTimeout(managed.timer);
67601
- this.loopTimers.delete(managed.timer);
67602
- }
67603
- this.intervalLoops.delete(loopId);
67604
- await this.deps.sessionState.removeIntervalLoop(this.deps.resolveTarget(managed.target), loopId);
67587
+ await this.cancelManagedIntervalLoop(managedKey, managed);
67605
67588
  return true;
67606
67589
  }
67607
67590
  async cancelIntervalLoopsForSession(target) {
67608
- const matching = [...this.intervalLoops.values()].filter((managed) => managed.target.sessionKey === target.sessionKey).map((managed) => managed.loop.id);
67609
- for (const loopId of matching) {
67610
- await this.cancelIntervalLoop(loopId);
67591
+ const matching = [...this.intervalLoops.entries()].filter(([, managed]) => managed.target.sessionKey === target.sessionKey);
67592
+ for (const [managedKey, managed] of matching) {
67593
+ await this.cancelManagedIntervalLoop(managedKey, managed);
67611
67594
  }
67612
67595
  return matching.length;
67613
67596
  }
67614
67597
  async cancelAllIntervalLoops() {
67615
- const ids = [...this.intervalLoops.keys()];
67616
- for (const loopId of ids) {
67617
- await this.cancelIntervalLoop(loopId);
67598
+ const matching = [...this.intervalLoops.entries()];
67599
+ for (const [managedKey, managed] of matching) {
67600
+ await this.cancelManagedIntervalLoop(managedKey, managed);
67618
67601
  }
67619
- return ids.length;
67602
+ return matching.length;
67620
67603
  }
67621
67604
  listIntervalLoops(params) {
67622
67605
  return [...this.intervalLoops.values()].filter((managed) => !params?.sessionKey || managed.target.sessionKey === params.sessionKey).map((managed) => this.toLoopStatus(managed)).sort((left, right) => left.nextRunAt - right.nextRunAt);
67623
67606
  }
67624
- getIntervalLoop(loopId) {
67625
- const managed = this.intervalLoops.get(loopId);
67607
+ getIntervalLoop(sessionKey, loopId) {
67608
+ const managed = this.intervalLoops.get(this.buildManagedLoopKey(sessionKey, loopId));
67626
67609
  return managed ? this.toLoopStatus(managed) : null;
67627
67610
  }
67628
67611
  getActiveIntervalLoopCount() {
@@ -67646,8 +67629,8 @@ class ManagedLoopController {
67646
67629
  const entry = await this.deps.sessionState.getEntry(managed.target.sessionKey);
67647
67630
  return (entry?.loops ?? entry?.intervalLoops ?? []).some((loop) => loop.id === managed.loop.id);
67648
67631
  }
67649
- dropManagedIntervalLoop(loopId) {
67650
- const managed = this.intervalLoops.get(loopId);
67632
+ dropManagedIntervalLoop(managedKey) {
67633
+ const managed = this.intervalLoops.get(managedKey);
67651
67634
  if (!managed) {
67652
67635
  return;
67653
67636
  }
@@ -67655,30 +67638,34 @@ class ManagedLoopController {
67655
67638
  clearTimeout(managed.timer);
67656
67639
  this.loopTimers.delete(managed.timer);
67657
67640
  }
67658
- this.intervalLoops.delete(loopId);
67641
+ this.intervalLoops.delete(managedKey);
67659
67642
  }
67660
- async runIntervalLoopIteration(loopId, options = {}) {
67661
- const managed = this.intervalLoops.get(loopId);
67643
+ async cancelManagedIntervalLoop(managedKey, managed) {
67644
+ this.dropManagedIntervalLoop(managedKey);
67645
+ await this.deps.sessionState.removeIntervalLoop(this.deps.resolveTarget(managed.target), managed.loop.id);
67646
+ }
67647
+ async runIntervalLoopIteration(managedKey, options = {}) {
67648
+ const managed = this.intervalLoops.get(managedKey);
67662
67649
  if (!managed) {
67663
67650
  return;
67664
67651
  }
67665
67652
  if (!await this.isManagedLoopPersisted(managed)) {
67666
- this.dropManagedIntervalLoop(loopId);
67653
+ this.dropManagedIntervalLoop(managedKey);
67667
67654
  return;
67668
67655
  }
67669
67656
  const attemptedRuns = managed.loop.attemptedRuns + 1;
67670
67657
  const now = Date.now();
67671
67658
  const nextLoopState = this.buildNextLoopState(managed.loop, attemptedRuns, now);
67672
67659
  if (await this.deps.isSessionBusy(managed.target)) {
67673
- await this.skipLoopIteration(loopId, managed, nextLoopState, attemptedRuns, now);
67660
+ await this.skipLoopIteration(managedKey, managed, nextLoopState, attemptedRuns, now);
67674
67661
  return;
67675
67662
  }
67676
67663
  nextLoopState.executedRuns += 1;
67677
67664
  if (!await this.updateManagedIntervalLoop(managed, nextLoopState)) {
67678
- this.dropManagedIntervalLoop(loopId);
67665
+ this.dropManagedIntervalLoop(managedKey);
67679
67666
  return;
67680
67667
  }
67681
- await this.executeLoopIteration(loopId, managed.target, nextLoopState, attemptedRuns, now, options.notifyStart !== false);
67668
+ await this.executeLoopIteration(managedKey, managed.target, nextLoopState, attemptedRuns, now, options.notifyStart !== false);
67682
67669
  }
67683
67670
  buildNextLoopState(loop, attemptedRuns, now) {
67684
67671
  return {
@@ -67688,33 +67675,35 @@ class ManagedLoopController {
67688
67675
  nextRunAt: this.computeNextManagedLoopRunAtMs(loop, now)
67689
67676
  };
67690
67677
  }
67691
- async skipLoopIteration(loopId, managed, nextLoopState, attemptedRuns, now) {
67678
+ async skipLoopIteration(managedKey, managed, nextLoopState, attemptedRuns, now) {
67692
67679
  nextLoopState.skippedRuns += 1;
67693
67680
  if (!await this.updateManagedIntervalLoop(managed, nextLoopState)) {
67694
- this.dropManagedIntervalLoop(loopId);
67681
+ this.dropManagedIntervalLoop(managedKey);
67695
67682
  return;
67696
67683
  }
67697
67684
  if (attemptedRuns >= managed.loop.maxRuns) {
67698
- await this.cancelIntervalLoop(loopId);
67685
+ this.dropManagedIntervalLoop(managedKey);
67686
+ await this.deps.sessionState.removeIntervalLoop(this.deps.resolveTarget(managed.target), managed.loop.id);
67699
67687
  return;
67700
67688
  }
67701
- this.scheduleIntervalLoopTimer(loopId, Math.max(0, nextLoopState.nextRunAt - now));
67689
+ this.scheduleIntervalLoopTimer(managedKey, Math.max(0, nextLoopState.nextRunAt - now));
67702
67690
  }
67703
- async executeLoopIteration(loopId, target, nextLoopState, attemptedRuns, now, notifyStart) {
67704
- await this.notifyAndEnqueueLoop(loopId, target, nextLoopState, attemptedRuns, notifyStart);
67691
+ async executeLoopIteration(managedKey, target, nextLoopState, attemptedRuns, now, notifyStart) {
67692
+ await this.notifyAndEnqueueLoop(managedKey, target, nextLoopState, attemptedRuns, notifyStart);
67705
67693
  if (attemptedRuns >= nextLoopState.maxRuns) {
67706
- await this.cancelIntervalLoop(loopId);
67694
+ this.dropManagedIntervalLoop(managedKey);
67695
+ await this.deps.sessionState.removeIntervalLoop(this.deps.resolveTarget(target), nextLoopState.id);
67707
67696
  return;
67708
67697
  }
67709
- this.scheduleIntervalLoopTimer(loopId, Math.max(0, nextLoopState.nextRunAt - now));
67698
+ this.scheduleIntervalLoopTimer(managedKey, Math.max(0, nextLoopState.nextRunAt - now));
67710
67699
  }
67711
- async notifyAndEnqueueLoop(loopId, target, nextLoopState, attemptedRuns, notifyStart) {
67700
+ async notifyAndEnqueueLoop(managedKey, target, nextLoopState, attemptedRuns, notifyStart) {
67712
67701
  if (notifyStart) {
67713
67702
  await this.deps.surfaceRuntime.notifyManagedLoopStart(target, nextLoopState);
67714
67703
  }
67715
67704
  const promptText = await this.deps.surfaceRuntime.buildManagedLoopPrompt(target.agentId, nextLoopState);
67716
67705
  const { result } = this.deps.enqueuePrompt(target, promptText, {
67717
- observerId: `loop:${loopId}:${attemptedRuns}`,
67706
+ observerId: `loop:${managedKey}:${attemptedRuns}`,
67718
67707
  onUpdate: async () => {
67719
67708
  return;
67720
67709
  }
@@ -67725,8 +67714,8 @@ class ManagedLoopController {
67725
67714
  }
67726
67715
  });
67727
67716
  }
67728
- scheduleIntervalLoopTimer(loopId, delayMs) {
67729
- const managed = this.intervalLoops.get(loopId);
67717
+ scheduleIntervalLoopTimer(managedKey, delayMs) {
67718
+ const managed = this.intervalLoops.get(managedKey);
67730
67719
  if (!managed) {
67731
67720
  return;
67732
67721
  }
@@ -67736,12 +67725,12 @@ class ManagedLoopController {
67736
67725
  }
67737
67726
  const timer = setTimeout(() => {
67738
67727
  this.loopTimers.delete(timer);
67739
- const current = this.intervalLoops.get(loopId);
67728
+ const current = this.intervalLoops.get(managedKey);
67740
67729
  if (!current) {
67741
67730
  return;
67742
67731
  }
67743
67732
  current.timer = undefined;
67744
- this.runIntervalLoopIteration(loopId, { notifyStart: true }).catch((error) => {
67733
+ this.runIntervalLoopIteration(managedKey, { notifyStart: true }).catch((error) => {
67745
67734
  if (!this.deps.shouldSuppressShutdownError(error)) {
67746
67735
  console.error("loop execution failed", error);
67747
67736
  }
@@ -67758,6 +67747,9 @@ class ManagedLoopController {
67758
67747
  managed.loop = nextLoopState;
67759
67748
  return true;
67760
67749
  }
67750
+ buildManagedLoopKey(sessionKey, loopId) {
67751
+ return `${sessionKey}::${loopId}`;
67752
+ }
67761
67753
  computeNextManagedLoopRunAtMs(loop, nowMs) {
67762
67754
  if (loop.kind === "calendar") {
67763
67755
  return computeNextCalendarLoopRunAtMs({
@@ -67798,7 +67790,7 @@ class ManagedQueueController {
67798
67790
  }
67799
67791
  async clearQueuedPrompts(target) {
67800
67792
  const clearedStored = await this.deps.sessionState.clearPendingQueuedItemsForSessionKey(target.sessionKey);
67801
- this.deps.queue.clearPendingByIds(clearedStored.map((item) => item.id));
67793
+ this.deps.queue.clearPendingByIdsForKey(target.sessionKey, clearedStored.map((item) => item.id));
67802
67794
  return clearedStored.length;
67803
67795
  }
67804
67796
  enqueuePrompt(target, prompt, callbacks) {
@@ -67836,8 +67828,8 @@ class ManagedQueueController {
67836
67828
  return;
67837
67829
  }
67838
67830
  this.setManagedQueueItem(target, item, true);
67839
- return this.persistQueueItem(this.deps.resolveTarget(target), item).then(() => this.markPersisted(item)).catch((error) => {
67840
- this.clearPendingById(item);
67831
+ return this.persistQueueItem(this.deps.resolveTarget(target), item).then(() => this.markPersisted(target, item)).catch((error) => {
67832
+ this.clearPendingById(target, item);
67841
67833
  throw error;
67842
67834
  });
67843
67835
  }
@@ -67848,16 +67840,32 @@ class ManagedQueueController {
67848
67840
  await reconciledBeforeStart;
67849
67841
  return !await this.deps.hasBlockingActiveRun(target);
67850
67842
  }
67843
+ async canStartPersistedQueueItem(target, item) {
67844
+ if (await this.deps.hasBlockingActiveRun(target)) {
67845
+ return false;
67846
+ }
67847
+ if (!await this.deps.sessionState.hasQueuedItem(target.sessionKey, item.id)) {
67848
+ this.clearPendingById(target, item);
67849
+ return false;
67850
+ }
67851
+ return true;
67852
+ }
67851
67853
  async reconcilePersistedQueueItems() {
67852
67854
  const persistedItems = await this.deps.sessionState.listQueuedItems({
67853
67855
  statuses: ["pending", "running"]
67854
67856
  });
67855
- const persistedIds = new Set(persistedItems.map((item) => item.id));
67856
- const removedIds = [...this.queuedItems.entries()].filter(([id, managed]) => !managed.persisting && !persistedIds.has(id)).map(([id]) => id);
67857
- if (removedIds.length > 0) {
67858
- this.deps.queue.clearPendingByIds(removedIds);
67859
- for (const id of removedIds) {
67860
- this.queuedItems.delete(id);
67857
+ const persistedKeys = new Set(persistedItems.map((item) => this.buildManagedQueueKey(item.sessionKey, item.id)));
67858
+ const removedManagedItems = [...this.queuedItems.entries()].filter(([key, managed]) => !managed.persisting && !persistedKeys.has(key));
67859
+ if (removedManagedItems.length > 0) {
67860
+ const removedBySession = new Map;
67861
+ for (const [key, managed] of removedManagedItems) {
67862
+ const ids = removedBySession.get(managed.target.sessionKey) ?? [];
67863
+ ids.push(managed.item.id);
67864
+ removedBySession.set(managed.target.sessionKey, ids);
67865
+ this.queuedItems.delete(key);
67866
+ }
67867
+ for (const [sessionKey, itemIds] of removedBySession.entries()) {
67868
+ this.deps.queue.clearPendingByIdsForKey(sessionKey, itemIds);
67861
67869
  }
67862
67870
  }
67863
67871
  for (const persisted of persistedItems) {
@@ -67865,14 +67873,14 @@ class ManagedQueueController {
67865
67873
  await this.clearStaleRunningQueueItem(persisted);
67866
67874
  continue;
67867
67875
  }
67868
- if (this.queuedItems.has(persisted.id)) {
67876
+ if (this.queuedItems.has(this.buildManagedQueueKey(persisted.sessionKey, persisted.id))) {
67869
67877
  continue;
67870
67878
  }
67871
67879
  this.enqueuePersistedQueueItem(persisted);
67872
67880
  }
67873
67881
  }
67874
67882
  async clearStaleRunningQueueItem(item) {
67875
- if (this.queuedItems.has(item.id)) {
67883
+ if (this.queuedItems.has(this.buildManagedQueueKey(item.sessionKey, item.id))) {
67876
67884
  return;
67877
67885
  }
67878
67886
  const target = {
@@ -67896,21 +67904,22 @@ class ManagedQueueController {
67896
67904
  });
67897
67905
  }
67898
67906
  setManagedQueueItem(target, item, persisting) {
67899
- this.queuedItems.set(item.id, {
67907
+ this.queuedItems.set(this.buildManagedQueueKey(target.sessionKey, item.id), {
67900
67908
  target,
67901
67909
  item,
67902
67910
  persisting
67903
67911
  });
67904
67912
  }
67905
- markPersisted(item) {
67906
- const managed = this.queuedItems.get(item.id);
67913
+ markPersisted(target, item) {
67914
+ const key = this.buildManagedQueueKey(target.sessionKey, item.id);
67915
+ const managed = this.queuedItems.get(key);
67907
67916
  if (managed) {
67908
- this.queuedItems.set(item.id, { ...managed, persisting: false });
67917
+ this.queuedItems.set(key, { ...managed, persisting: false });
67909
67918
  }
67910
67919
  }
67911
- clearPendingById(item) {
67912
- this.deps.queue.clearPendingByIds([item.id]);
67913
- this.queuedItems.delete(item.id);
67920
+ clearPendingById(target, item) {
67921
+ this.deps.queue.clearPendingByIdsForKey(target.sessionKey, [item.id]);
67922
+ this.queuedItems.delete(this.buildManagedQueueKey(target.sessionKey, item.id));
67914
67923
  }
67915
67924
  async markQueueItemRunning(target, item) {
67916
67925
  if (!item) {
@@ -67923,7 +67932,7 @@ class ManagedQueueController {
67923
67932
  startedAt: now,
67924
67933
  updatedAt: now
67925
67934
  };
67926
- this.queuedItems.set(item.id, {
67935
+ this.queuedItems.set(this.buildManagedQueueKey(target.sessionKey, item.id), {
67927
67936
  target,
67928
67937
  item: next
67929
67938
  });
@@ -67934,7 +67943,7 @@ class ManagedQueueController {
67934
67943
  if (!item) {
67935
67944
  return;
67936
67945
  }
67937
- this.queuedItems.delete(item.id);
67946
+ this.queuedItems.delete(this.buildManagedQueueKey(target.sessionKey, item.id));
67938
67947
  await this.deps.sessionState.removeQueuedItem(this.deps.resolveTarget(target), item.id);
67939
67948
  }
67940
67949
  enqueuePersistedQueueItem(item) {
@@ -67942,7 +67951,7 @@ class ManagedQueueController {
67942
67951
  agentId: item.agentId,
67943
67952
  sessionKey: item.sessionKey
67944
67953
  };
67945
- this.queuedItems.set(item.id, {
67954
+ this.queuedItems.set(this.buildManagedQueueKey(target.sessionKey, item.id), {
67946
67955
  target,
67947
67956
  item
67948
67957
  });
@@ -67974,7 +67983,7 @@ class ManagedQueueController {
67974
67983
  id: item.id,
67975
67984
  createdAt: item.createdAt,
67976
67985
  text: item.promptSummary,
67977
- canStart: async () => !await this.deps.hasBlockingActiveRun(target),
67986
+ canStart: async () => this.canStartPersistedQueueItem(target, item),
67978
67987
  onComplete: async (value) => {
67979
67988
  if (!value.messageToolFinalAlreadySent && !await this.hasMessageToolFinalForQueueItem(target, item)) {
67980
67989
  await this.deps.surfaceRuntime.notifyManagedQueueSettlement(target, item, value);
@@ -67988,6 +67997,9 @@ class ManagedQueueController {
67988
67997
  onClear: () => this.removeManagedQueueItem(target, item)
67989
67998
  });
67990
67999
  queued.result.catch((error) => {
68000
+ if (error instanceof ClearedQueuedTaskError) {
68001
+ return;
68002
+ }
67991
68003
  if (this.deps.shouldSuppressShutdownError(error)) {
67992
68004
  return;
67993
68005
  }
@@ -68004,7 +68016,7 @@ class ManagedQueueController {
68004
68016
  return false;
68005
68017
  }
68006
68018
  async hasMessageToolFinalForQueueItem(target, item) {
68007
- const startedAt = this.queuedItems.get(item.id)?.item.startedAt ?? item.startedAt;
68019
+ const startedAt = this.queuedItems.get(this.buildManagedQueueKey(target.sessionKey, item.id))?.item.startedAt ?? item.startedAt;
68008
68020
  if (typeof startedAt !== "number" || !Number.isFinite(startedAt)) {
68009
68021
  return false;
68010
68022
  }
@@ -68025,6 +68037,9 @@ class ManagedQueueController {
68025
68037
  messageToolFinalAlreadySent: true
68026
68038
  };
68027
68039
  }
68040
+ buildManagedQueueKey(sessionKey, itemId) {
68041
+ return `${sessionKey}::${itemId}`;
68042
+ }
68028
68043
  }
68029
68044
 
68030
68045
  // src/agents/runner-service.ts
@@ -69731,7 +69746,11 @@ class RunnerService {
69731
69746
  args: args.map((value) => applyTemplate(value, values))
69732
69747
  };
69733
69748
  }
69734
- async syncSessionIdentity(resolved) {
69749
+ async syncStoredSessionId(target) {
69750
+ const resolved = this.resolveTarget(target);
69751
+ return this.syncStoredSessionIdForResolvedTarget(resolved);
69752
+ }
69753
+ async syncStoredSessionIdForResolvedTarget(resolved) {
69735
69754
  const existing = await this.sessionState.getEntry(resolved.sessionKey);
69736
69755
  if (existing?.sessionId) {
69737
69756
  this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
@@ -69793,12 +69812,41 @@ class RunnerService {
69793
69812
  remainingFreshRetries: remainingFreshRetries - 1
69794
69813
  });
69795
69814
  }
69796
- async retryAfterStartupFault(target, resolved, error, remainingFreshRetries) {
69815
+ async retryAfterStartupFault(target, resolved, error, remainingFreshRetries, allowFreshResumeFallback) {
69816
+ if (allowFreshResumeFallback) {
69817
+ const resumedFresh = await this.retryFreshStartAfterStoredResumeFailure(target, resolved, error, remainingFreshRetries);
69818
+ if (resumedFresh) {
69819
+ return resumedFresh;
69820
+ }
69821
+ }
69797
69822
  if (!isRetryableFreshStartFault(error)) {
69798
69823
  return null;
69799
69824
  }
69800
69825
  return this.retryRunnerRestartPreservingSessionId(target, resolved, remainingFreshRetries);
69801
69826
  }
69827
+ async retryFreshStartAfterStoredResumeFailure(target, resolved, error, remainingFreshRetries) {
69828
+ if (!isRecoverableStartupSessionLoss(error)) {
69829
+ return null;
69830
+ }
69831
+ if (resolved.runner.sessionId.resume.mode !== "command" || resolved.runner.sessionId.create.mode !== "runner") {
69832
+ return null;
69833
+ }
69834
+ const existing = await this.sessionState.getEntry(resolved.sessionKey);
69835
+ if (!existing?.sessionId) {
69836
+ return null;
69837
+ }
69838
+ const exitRecord = await readRunnerExitRecord(this.loadedConfig.stateDir, resolved.sessionName);
69839
+ if (!exitRecord || exitRecord.exitCode === 0) {
69840
+ return null;
69841
+ }
69842
+ console.log(`clisbot clearing stored sessionId after failed runner resume startup ${resolved.sessionName}`);
69843
+ await this.sessionState.clearSessionIdEntry(resolved, {
69844
+ runnerCommand: resolved.runner.command
69845
+ });
69846
+ return this.ensureSessionReady(target, {
69847
+ remainingFreshRetries
69848
+ });
69849
+ }
69802
69850
  async retryAfterStartupTimeout(target, resolved, remainingFreshRetries) {
69803
69851
  return this.retryRunnerRestartPreservingSessionId(target, resolved, remainingFreshRetries);
69804
69852
  }
@@ -69897,7 +69945,7 @@ class RunnerService {
69897
69945
  });
69898
69946
  try {
69899
69947
  await clearRunnerExitRecord(this.loadedConfig.stateDir, resolved.sessionName);
69900
- await this.syncSessionIdentity(resolved);
69948
+ await this.syncStoredSessionIdForResolvedTarget(resolved);
69901
69949
  } catch (error) {
69902
69950
  throw await this.mapSessionError(error, resolved.sessionName, "during startup");
69903
69951
  }
@@ -69967,7 +70015,7 @@ class RunnerService {
69967
70015
  runnerCommand: runnerLaunch.command
69968
70016
  });
69969
70017
  } catch (error) {
69970
- const retried = await this.retryAfterStartupFault(target, resolved, error, remainingFreshRetries);
70018
+ const retried = await this.retryAfterStartupFault(target, resolved, error, remainingFreshRetries, options.allowFreshRetry !== false);
69971
70019
  if (retried) {
69972
70020
  return retried;
69973
70021
  }
@@ -69989,7 +70037,7 @@ class RunnerService {
69989
70037
  });
69990
70038
  return;
69991
70039
  }
69992
- await this.syncSessionIdentity(resolved);
70040
+ await this.syncStoredSessionIdForResolvedTarget(resolved);
69993
70041
  }
69994
70042
  async dismissTrustPrompt(resolved) {
69995
70043
  if (!resolved.runner.trustWorkspace) {
@@ -70522,16 +70570,21 @@ class SessionService {
70522
70570
  this.resolveTarget = resolveTarget;
70523
70571
  }
70524
70572
  async recoverPersistedRuns() {
70573
+ const activeSessionKeys = new Set;
70525
70574
  const entries = await this.sessionState.listEntries();
70526
70575
  for (const entry of entries) {
70527
70576
  if (!entry.runtime || entry.runtime.state === "idle") {
70528
70577
  continue;
70529
70578
  }
70530
- await this.reconcilePersistedActiveRun({
70579
+ const run = await this.reconcilePersistedActiveRun({
70531
70580
  agentId: entry.agentId,
70532
70581
  sessionKey: entry.sessionKey
70533
70582
  });
70583
+ if (run) {
70584
+ activeSessionKeys.add(run.resolved.sessionKey);
70585
+ }
70534
70586
  }
70587
+ return activeSessionKeys;
70535
70588
  }
70536
70589
  async clearLostPersistedActiveRuns() {
70537
70590
  const entries = await this.sessionState.listEntries();
@@ -70762,6 +70815,14 @@ class SessionService {
70762
70815
  }
70763
70816
  this.activeRuns.clear();
70764
70817
  }
70818
+ listLiveSessionRuntimes() {
70819
+ return [...this.activeRuns.values()].map((run) => ({
70820
+ state: run.latestUpdate.status === "detached" ? "detached" : "running",
70821
+ startedAt: run.startedAt,
70822
+ sessionKey: run.resolved.sessionKey,
70823
+ agentId: run.resolved.agentId
70824
+ }));
70825
+ }
70765
70826
  buildDetachedNote(resolved) {
70766
70827
  return `This session has been running for over ${resolved.stream.maxRuntimeLabel}. clisbot left it running and will post the final result here when it completes. Use \`/attach\` for live updates, \`/watch every <duration>\` for periodic updates, or \`/stop\` to interrupt it.`;
70767
70828
  }
@@ -72141,7 +72202,7 @@ class SurfaceRuntime {
72141
72202
  }
72142
72203
  }
72143
72204
  async buildManagedLoopPrompt(agentId, loop) {
72144
- if (!loop.canonicalPromptText || !loop.surfaceBinding) {
72205
+ if (!loop.surfaceBinding) {
72145
72206
  return loop.promptText;
72146
72207
  }
72147
72208
  const identity = this.buildLoopChannelIdentity(loop);
@@ -72156,7 +72217,7 @@ class SurfaceRuntime {
72156
72217
  scheduledLoopId: loop.id
72157
72218
  });
72158
72219
  return buildAgentPromptText({
72159
- text: loop.canonicalPromptText,
72220
+ text: loop.promptText,
72160
72221
  identity,
72161
72222
  config: channelConfig.agentPrompt,
72162
72223
  cliTool: getAgentEntry2(this.loadedConfig, agentId)?.cli,
@@ -72170,7 +72231,7 @@ class SurfaceRuntime {
72170
72231
  });
72171
72232
  }
72172
72233
  async buildManagedQueuePrompt(agentId, item) {
72173
- if (!item.canonicalPromptText || !item.surfaceBinding) {
72234
+ if (!item.surfaceBinding) {
72174
72235
  return item.promptText;
72175
72236
  }
72176
72237
  const identity = this.buildQueueChannelIdentity(item);
@@ -72184,7 +72245,7 @@ class SurfaceRuntime {
72184
72245
  time: promptTime
72185
72246
  });
72186
72247
  return buildAgentPromptText({
72187
- text: item.canonicalPromptText,
72248
+ text: item.promptText,
72188
72249
  identity,
72189
72250
  config: channelConfig.agentPrompt,
72190
72251
  cliTool: getAgentEntry2(this.loadedConfig, agentId)?.cli,
@@ -72407,9 +72468,8 @@ class AgentService {
72407
72468
  });
72408
72469
  }
72409
72470
  async start() {
72410
- await this.activeRuns.recoverPersistedRuns();
72411
- const activeSessions = new Set((await this.sessionState.listActiveSessionRuntimes()).map((runtime) => runtime.sessionKey));
72412
- await this.sessionState.resetStaleRunningQueuedItems(activeSessions);
72471
+ const activeSessionKeys = await this.activeRuns.recoverPersistedRuns();
72472
+ await this.sessionState.resetStaleRunningQueuedItems(activeSessionKeys);
72413
72473
  await this.managedQueues.reconcilePersistedQueueItems();
72414
72474
  this.queueReconcileTimer = setInterval(() => {
72415
72475
  this.managedQueues.reconcilePersistedQueueItems().catch((error) => {
@@ -72492,14 +72552,13 @@ class AgentService {
72492
72552
  const storedSessionId = entry?.sessionId?.trim() || undefined;
72493
72553
  return {
72494
72554
  sessionName: resolved.sessionName,
72495
- sessionId: storedSessionId,
72496
72555
  storedSessionId,
72497
72556
  resumeCommand: buildResumeCommandPreview(resolved, storedSessionId)
72498
72557
  };
72499
72558
  }
72500
- async listActiveSessionRuntimes() {
72559
+ async listLiveSessionRuntimes() {
72501
72560
  await this.activeRuns.clearLostPersistedActiveRuns();
72502
- return this.sessionState.listActiveSessionRuntimes();
72561
+ return this.activeRuns.listLiveSessionRuntimes();
72503
72562
  }
72504
72563
  async setConversationFollowUpMode(target, mode) {
72505
72564
  return this.sessionState.setConversationFollowUpMode(this.resolveTarget(target), mode);
@@ -72600,8 +72659,8 @@ class AgentService {
72600
72659
  async createCalendarLoop(params) {
72601
72660
  return this.managedLoops.createCalendarLoop(params);
72602
72661
  }
72603
- async cancelIntervalLoop(loopId) {
72604
- return this.managedLoops.cancelIntervalLoop(loopId);
72662
+ async cancelIntervalLoop(target, loopId) {
72663
+ return this.managedLoops.cancelIntervalLoop(target, loopId);
72605
72664
  }
72606
72665
  async cancelIntervalLoopsForSession(target) {
72607
72666
  return this.managedLoops.cancelIntervalLoopsForSession(target);
@@ -72612,8 +72671,8 @@ class AgentService {
72612
72671
  listIntervalLoops(params) {
72613
72672
  return this.managedLoops.listIntervalLoops(params);
72614
72673
  }
72615
- getIntervalLoop(loopId) {
72616
- return this.managedLoops.getIntervalLoop(loopId);
72674
+ getIntervalLoop(target, loopId) {
72675
+ return this.managedLoops.getIntervalLoop(target.sessionKey, loopId);
72617
72676
  }
72618
72677
  getActiveIntervalLoopCount() {
72619
72678
  return this.managedLoops.getActiveIntervalLoopCount();
@@ -72665,9 +72724,8 @@ function createStoredQueueItem(params) {
72665
72724
  createdAt: now,
72666
72725
  updatedAt: now,
72667
72726
  promptText: params.promptText,
72668
- canonicalPromptText: params.canonicalPromptText,
72669
72727
  protectedControlMutationRule: params.protectedControlMutationRule,
72670
- promptSummary: params.promptSummary ?? summarizeQueuePrompt(params.canonicalPromptText ?? params.promptText),
72728
+ promptSummary: params.promptSummary ?? summarizeQueuePrompt(params.promptText),
72671
72729
  promptSource: "custom",
72672
72730
  createdBy: params.createdBy,
72673
72731
  sender: params.sender,
@@ -72919,7 +72977,6 @@ function renderStartupSteeringUnavailableMessage() {
72919
72977
  `);
72920
72978
  }
72921
72979
  function renderWhoAmIMessage(params) {
72922
- const storedSessionId = params.sessionDiagnostics.storedSessionId ?? params.sessionDiagnostics.sessionId;
72923
72980
  const lines = [
72924
72981
  "Who am I",
72925
72982
  "",
@@ -72927,7 +72984,7 @@ function renderWhoAmIMessage(params) {
72927
72984
  `conversationKind: \`${params.identity.conversationKind}\``,
72928
72985
  `agentId: \`${params.route.agentId}\``,
72929
72986
  `sessionName: \`${params.sessionDiagnostics.sessionName ?? "(not available)"}\``,
72930
- `storedSessionId: \`${storedSessionId ?? "(not captured yet)"}\``
72987
+ `storedSessionId: \`${params.sessionDiagnostics.storedSessionId ?? "(not captured yet)"}\``
72931
72988
  ];
72932
72989
  if (params.identity.senderId) {
72933
72990
  lines.push(`senderId: \`${params.identity.senderId}\``);
@@ -72949,7 +73006,6 @@ function renderWhoAmIMessage(params) {
72949
73006
  `);
72950
73007
  }
72951
73008
  function renderRouteStatusMessage(params) {
72952
- const storedSessionId = params.sessionDiagnostics.storedSessionId ?? params.sessionDiagnostics.sessionId;
72953
73009
  const lines = [
72954
73010
  "Status",
72955
73011
  "",
@@ -72957,7 +73013,7 @@ function renderRouteStatusMessage(params) {
72957
73013
  `conversationKind: \`${params.identity.conversationKind}\``,
72958
73014
  `agentId: \`${params.route.agentId}\``,
72959
73015
  `sessionName: \`${params.sessionDiagnostics.sessionName ?? "(not available)"}\``,
72960
- `storedSessionId: \`${storedSessionId ?? "(not captured yet)"}\``
73016
+ `storedSessionId: \`${params.sessionDiagnostics.storedSessionId ?? "(not captured yet)"}\``
72961
73017
  ];
72962
73018
  if (params.identity.senderId) {
72963
73019
  lines.push(`senderId: \`${params.identity.senderId}\``);
@@ -73734,7 +73790,6 @@ async function processChannelInteraction(params) {
73734
73790
  };
73735
73791
  const queueItem = forceQueuedDelivery ? createStoredQueueItem({
73736
73792
  promptText: delayedPromptQueueText,
73737
- canonicalPromptText: delayedPromptQueueText,
73738
73793
  promptSummary: explicitQueueMessage ?? summarizeQueuePrompt(delayedPromptQueueText),
73739
73794
  createdBy: params.senderId,
73740
73795
  sender: buildLoopSender(params.identity),
@@ -74047,7 +74102,7 @@ async function processChannelInteraction(params) {
74047
74102
  await params.agentService.recordConversationReply(params.sessionTarget);
74048
74103
  return interactionResult;
74049
74104
  }
74050
- const cancelled = await params.agentService.cancelIntervalLoop(targetLoopId);
74105
+ const cancelled = await params.agentService.cancelIntervalLoop(params.sessionTarget, targetLoopId);
74051
74106
  await params.postText(cancelled ? `Cancelled loop \`${targetLoopId}\`.` : `No active loop found with id \`${targetLoopId}\`.`);
74052
74107
  await params.agentService.recordConversationReply(params.sessionTarget);
74053
74108
  return interactionResult;
@@ -74132,7 +74187,6 @@ ${renderLoopUsage()}`);
74132
74187
  const createdLoop2 = await params.agentService.createCalendarLoop({
74133
74188
  target: params.sessionTarget,
74134
74189
  promptText: resolvedLoopPrompt.text,
74135
- canonicalPromptText: resolvedLoopPrompt.text,
74136
74190
  promptSummary: summarizeLoopPrompt(resolvedLoopPrompt.text, resolvedLoopPrompt.maintenancePrompt),
74137
74191
  promptSource: resolvedLoopPrompt.maintenancePrompt ? "LOOP.md" : "custom",
74138
74192
  loopStart: slashCommand.params.loopStart,
@@ -74172,7 +74226,6 @@ ${renderLoopUsage()}`);
74172
74226
  const createdLoop = await params.agentService.createIntervalLoop({
74173
74227
  target: params.sessionTarget,
74174
74228
  promptText: resolvedLoopPrompt.text,
74175
- canonicalPromptText: resolvedLoopPrompt.text,
74176
74229
  promptSummary: summarizeLoopPrompt(resolvedLoopPrompt.text, resolvedLoopPrompt.maintenancePrompt),
74177
74230
  promptSource: resolvedLoopPrompt.maintenancePrompt ? "LOOP.md" : "custom",
74178
74231
  loopStart: slashCommand.params.loopStart,
@@ -75661,9 +75714,9 @@ function getSlackMaxChars(maxMessageChars) {
75661
75714
  }
75662
75715
 
75663
75716
  // src/channels/runtime-identity.ts
75664
- import { createHash } from "node:crypto";
75717
+ import { createHash as createHash2 } from "node:crypto";
75665
75718
  function buildTokenHint(token) {
75666
- return createHash("sha256").update(token.trim()).digest("hex").slice(0, 8);
75719
+ return createHash2("sha256").update(token.trim()).digest("hex").slice(0, 8);
75667
75720
  }
75668
75721
 
75669
75722
  // src/channels/slack/service.ts
@@ -82062,7 +82115,6 @@ function buildRecurringLoopCreateBase(state, request) {
82062
82115
  function buildRecurringLoopPromptMetadata(request) {
82063
82116
  return {
82064
82117
  promptText: request.resolvedPrompt.text,
82065
- canonicalPromptText: request.resolvedPrompt.text,
82066
82118
  promptSummary: summarizeLoopPrompt(request.resolvedPrompt.text, request.resolvedPrompt.maintenancePrompt),
82067
82119
  promptSource: request.resolvedPrompt.maintenancePrompt ? "LOOP.md" : "custom",
82068
82120
  loopStart: request.parsed.mode === "times" ? undefined : request.parsed.loopStart,
@@ -82733,7 +82785,6 @@ function resolveProtectedControlMutationRule(state, agentId, sender) {
82733
82785
  function createQueueItemForContext(params) {
82734
82786
  return createStoredQueueItem({
82735
82787
  promptText: params.promptText,
82736
- canonicalPromptText: params.promptText,
82737
82788
  protectedControlMutationRule: resolveProtectedControlMutationRule(params.state, params.context.sessionTarget.agentId, params.sender),
82738
82789
  promptSummary: params.promptText,
82739
82790
  createdBy: params.sender.providerId,
@@ -84282,18 +84333,27 @@ function renderUpdateHelp() {
84282
84333
  " 2. Read docs in priority order and follow them before installing.",
84283
84334
  " 3. npm install -g clisbot@<target> && clisbot restart",
84284
84335
  ` 4. ${renderCliCommand("status")}`,
84285
- " 5. Report version, health, manual action, and useful release-note highlights.",
84336
+ " 5. Report version, health, manual action, and useful release highlights.",
84286
84337
  "",
84287
84338
  "Docs, read in order:",
84288
84339
  ` 1. Migration index: ${GITHUB_RAW_BASE}/docs/migrations/index.md`,
84289
84340
  " If Manual action: required, follow its runbook. If none, continue.",
84290
- ` 2. Release quick info: ${GITHUB_RAW_BASE}/docs/releases/README.md`,
84291
- " Use for release-note map and current release highlights.",
84292
- ` 3. Update guide: ${GITHUB_RAW_BASE}/docs/update/README.md`,
84293
- " Use for release notes, useful new features, things to try, and user-guide links.",
84294
- " 4. Full docs: https://github.com/longbkit/clisbot/tree/main/docs",
84341
+ ` 2. Update guide: ${GITHUB_RAW_BASE}/docs/updates/update-guide.md`,
84342
+ " Use for target choice, install flow, verification, and wrong-publish recovery.",
84343
+ ` 3. Release notes: ${GITHUB_RAW_BASE}/docs/releases/README.md`,
84344
+ " Use for the canonical version map and full version notes.",
84345
+ ` 4. Release guides: ${GITHUB_RAW_BASE}/docs/updates/README.md`,
84346
+ " Use for shorter catch-up notes: what changed, what to try, and what to watch.",
84347
+ " 5. Full docs: https://github.com/longbkit/clisbot/tree/main/docs",
84295
84348
  " Use for deep questions. If needed, fetch or clone docs and inspect relevant files.",
84296
84349
  "",
84350
+ "Recovery:",
84351
+ " - If a version was published by mistake, publish the corrected target or tag first.",
84352
+ " - Then deprecate the wrong version.",
84353
+ " - Start with `npm login` in an attached session.",
84354
+ " - If npm returns a browser approval URL, keep that same session open and continue it after approval.",
84355
+ " - If the write command still returns EOTP, ask the operator for a current OTP and rerun the exact command with --otp=<code>.",
84356
+ "",
84297
84357
  "Rules:",
84298
84358
  " - Use npm dist-tags, not highest semver.",
84299
84359
  " - Stable/latest is default; beta only when the user asks.",
@@ -84847,7 +84907,7 @@ async function getRuntimeOperatorSummary(params) {
84847
84907
  },
84848
84908
  agentSummaries,
84849
84909
  channelSummaries,
84850
- activeRuns: await agentService.listActiveSessionRuntimes(),
84910
+ activeRuns: await agentService.listLiveSessionRuntimes(),
84851
84911
  configuredAgents: agentSummaries.length,
84852
84912
  bootstrapPendingAgents: agentSummaries.filter((item) => item.bootstrapState === "missing" || item.bootstrapState === "not-bootstrapped").length,
84853
84913
  bootstrappedAgents: agentSummaries.filter((item) => item.bootstrapState === "bootstrapped").length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clisbot",
3
- "version": "0.1.45-beta.7",
3
+ "version": "0.1.45-beta.9",
4
4
  "private": false,
5
5
  "description": "Chat surfaces for durable AI coding agents running in tmux",
6
6
  "license": "MIT",