codeam-cli 2.60.63 → 2.60.65

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.60.64] — 2026-07-15
8
+
9
+ ### Added
10
+
11
+ - **shared:** Add Slack search:read scope (agent can search the user's messages)
12
+
13
+ ### Documentation
14
+
15
+ - **shared:** Clarify Slack uses a USER token (agent acts as the user)
16
+
17
+ ## [2.60.63] — 2026-07-15
18
+
19
+ ### Added
20
+
21
+ - **shared:** Add Slack as a live Agent Toolkit integration
22
+
7
23
  ## [2.60.62] — 2026-07-15
8
24
 
9
25
  ### Added
package/dist/index.js CHANGED
@@ -614,19 +614,20 @@ var INTEGRATION_REGISTRY = {
614
614
  id: "slack",
615
615
  name: "Slack",
616
616
  icon: "slack",
617
- // LIVE — the Slack app (OAuth v2, bot token) is registered and
617
+ // LIVE — the Slack app (OAuth v2, USER token the agent acts AS THE USER)
618
+ // is registered and
618
619
  // SLACK_OAUTH_CLIENT_ID/SECRET/REDIRECT_URI are in Secret Manager
619
620
  // (prod+dev). The backend SlackOAuthProvider is config-gated (503 if env
620
621
  // unset) so this is safe even mid-rollout before the secrets mount.
621
622
  enabled: true,
622
623
  auth: {
623
624
  kind: "oauth_redirect",
624
- // Slack Bot Token Scopes (OAuth v2). Read + write across channels,
625
- // groups, DMs: list/read history, post messages, react. `scope` is
626
- // COMMA-separated in the authorize URL. ⚠️ Changing these requires the
627
- // user to RE-INSTALL the Slack app the bot token only carries the
628
- // scopes granted at install time. The bot can only read channels it's
629
- // been invited to (standard Slack bot behavior).
625
+ // Slack USER Token Scopes (OAuth v2). Read + write across channels,
626
+ // groups, DMs: list/read history, post messages, react all AS THE USER.
627
+ // The backend provider requests these under `user_scope` (comma-separated)
628
+ // and stores the authed_user `xoxp-…` token, so the agent sees everything
629
+ // the user sees (no bot needs to be invited to channels). ⚠️ Changing
630
+ // these requires the user to RE-AUTHORIZE the Slack app.
630
631
  scopes: [
631
632
  "channels:read",
632
633
  "channels:history",
@@ -639,15 +640,18 @@ var INTEGRATION_REGISTRY = {
639
640
  "im:read",
640
641
  "im:history",
641
642
  "mpim:read",
642
- "mpim:history"
643
+ "mpim:history",
644
+ // Message search across the user's channels/DMs (a user-only scope).
645
+ "search:read"
643
646
  ]
644
647
  },
645
648
  delivery: {
646
649
  mcp: {
647
650
  // Slack's official reference MCP server (Node). BYO-token headless: the
648
- // OAuth v2 bot token (xoxb-…) is fed via SLACK_BOT_TOKEN and the team
649
- // id via SLACK_TEAM_ID (env only, never argv). The server sends the
650
- // token as `Authorization: Bearer`. Version PINNED; bump only after
651
+ // OAuth v2 USER token (xoxp-…) is fed via SLACK_BOT_TOKEN (the server's
652
+ // env var name it sends whatever token as `Authorization: Bearer`, and
653
+ // Slack's Web API accepts a user token there) and the team id via
654
+ // SLACK_TEAM_ID (env only, never argv). Version PINNED; bump only after
651
655
  // re-verifying headless. Tools: list_channels, post_message,
652
656
  // reply_to_thread, add_reaction, get_channel_history,
653
657
  // get_thread_replies, get_users, get_user_profile (read + write).
@@ -5887,7 +5891,7 @@ function readAnonId() {
5887
5891
  }
5888
5892
  function superProperties() {
5889
5893
  return {
5890
- cliVersion: true ? "2.60.63" : "0.0.0-dev",
5894
+ cliVersion: true ? "2.60.65" : "0.0.0-dev",
5891
5895
  nodeVersion: process.version,
5892
5896
  platform: process.platform,
5893
5897
  arch: process.arch,
@@ -6068,7 +6072,7 @@ var os4 = __toESM(require("os"));
6068
6072
  // package.json
6069
6073
  var package_default = {
6070
6074
  name: "codeam-cli",
6071
- version: "2.60.63",
6075
+ version: "2.60.65",
6072
6076
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
6073
6077
  type: "commonjs",
6074
6078
  main: "dist/index.js",
@@ -6836,7 +6840,7 @@ function httpStatusOf(err) {
6836
6840
  var API_BASE2 = resolveApiBaseUrl();
6837
6841
  var SSE_LIVENESS_TIMEOUT_MS = 45e3;
6838
6842
  var SSE_WATCHDOG_INTERVAL_MS = 1e4;
6839
- var CommandRelayService = class {
6843
+ var CommandRelayService = class _CommandRelayService {
6840
6844
  constructor(pluginId, onCommand, agentMeta, agentsOverride) {
6841
6845
  this.pluginId = pluginId;
6842
6846
  this.onCommand = onCommand;
@@ -6858,6 +6862,16 @@ var CommandRelayService = class {
6858
6862
  agentsRegistered = false;
6859
6863
  /** SSE connection (null when on the polling fallback or stopped). */
6860
6864
  sseRequest = null;
6865
+ // At-least-once delivery: the backend now delivers commands NON-DESTRUCTIVELY
6866
+ // (peek) when we advertise `X-Codeam-Cmd-Ack: 1`, and drains the queue only
6867
+ // when we ack the ids. So a command can never be lost to a ghost/racing SSE
6868
+ // subscriber during codespace boot (the 2026-07-15 first-prompt loss). The
6869
+ // trade-off is possible REDELIVERY (reconnect, publish re-fire, ghost race),
6870
+ // so we dedupe by id here — a bounded FIFO set of ids we've already
6871
+ // dispatched. Cap keeps memory flat over a long session; the oldest ids are
6872
+ // evicted (a command that old is long gone from the server queue anyway).
6873
+ processedIds = /* @__PURE__ */ new Set();
6874
+ static PROCESSED_ID_CAP = 1e3;
6861
6875
  /** Polling backoff state (only used on the fallback). */
6862
6876
  pollTimer = null;
6863
6877
  pollFailures = 0;
@@ -7127,14 +7141,15 @@ var CommandRelayService = class {
7127
7141
  // as X-Plugin-Poll-Secret. Empty {} for legacy sessions / older
7128
7142
  // backends (which ignore it).
7129
7143
  pollSecretHeader() {
7144
+ const headers = { "X-Codeam-Cmd-Ack": "1" };
7130
7145
  try {
7131
7146
  const secret = loadCliConfig().sessions.find(
7132
7147
  (s) => s.pluginId === this.pluginId
7133
7148
  )?.pollSecret;
7134
- return secret ? { "X-Plugin-Poll-Secret": secret } : {};
7149
+ if (secret) headers["X-Plugin-Poll-Secret"] = secret;
7135
7150
  } catch {
7136
- return {};
7137
7151
  }
7152
+ return headers;
7138
7153
  }
7139
7154
  async pollOnce() {
7140
7155
  try {
@@ -7157,7 +7172,13 @@ var CommandRelayService = class {
7157
7172
  }
7158
7173
  }
7159
7174
  async dispatchCommands(commands) {
7175
+ this.ackCommands(commands.map((c2) => c2.id));
7160
7176
  for (const cmd of commands) {
7177
+ if (cmd.id && this.processedIds.has(cmd.id)) {
7178
+ log.trace("relay", `dedupe skip already-dispatched id=${cmd.id}`);
7179
+ continue;
7180
+ }
7181
+ if (cmd.id) this.rememberProcessed(cmd.id);
7161
7182
  try {
7162
7183
  log.trace("relay", `dispatch type=${cmd.type} id=${cmd.id}`);
7163
7184
  await this.onCommand(cmd);
@@ -7166,6 +7187,23 @@ var CommandRelayService = class {
7166
7187
  }
7167
7188
  }
7168
7189
  }
7190
+ /** Record a dispatched id, evicting the oldest when over the cap (FIFO). */
7191
+ rememberProcessed(id) {
7192
+ this.processedIds.add(id);
7193
+ if (this.processedIds.size > _CommandRelayService.PROCESSED_ID_CAP) {
7194
+ const oldest = this.processedIds.values().next().value;
7195
+ if (oldest !== void 0) this.processedIds.delete(oldest);
7196
+ }
7197
+ }
7198
+ /** Confirm receipt of command ids so the backend removes them from the queue
7199
+ * (the at-least-once delivery guarantee). Best-effort — never throws. */
7200
+ ackCommands(ids) {
7201
+ const commandIds = ids.filter((id) => typeof id === "string" && id.length > 0);
7202
+ if (commandIds.length === 0) return;
7203
+ void _postJson(`${API_BASE2}/api/commands/ack`, { pluginId: this.pluginId, commandIds }, {
7204
+ ...this.pollSecretHeader()
7205
+ }).catch((err) => log.trace("relay", "ack post failed (will redeliver+dedupe)", err));
7206
+ }
7169
7207
  // ─── Heartbeat + agents ──────────────────────────────────────────
7170
7208
  async sendHeartbeat(online) {
7171
7209
  await _postJson(`${API_BASE2}/api/plugin/heartbeat`, {
@@ -7177,7 +7215,7 @@ var CommandRelayService = class {
7177
7215
  // fresh + clear the "CLI update available" banner after a self-update
7178
7216
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
7179
7217
  // pair/reconnect). Older backends ignore the extra field.
7180
- ..."2.60.63" ? { ideVersion: "2.60.63" } : {}
7218
+ ..."2.60.65" ? { ideVersion: "2.60.65" } : {}
7181
7219
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
7182
7220
  }
7183
7221
  /**
@@ -17754,7 +17792,7 @@ async function autoUpgradeBeforeCriticalCommand() {
17754
17792
  if (process.env.NODE_ENV === "test") return;
17755
17793
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17756
17794
  if (process.env.CI) return;
17757
- const current = true ? "2.60.63" : null;
17795
+ const current = true ? "2.60.65" : null;
17758
17796
  if (!current) return;
17759
17797
  const cache = readCache();
17760
17798
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17771,7 +17809,7 @@ function checkForUpdates() {
17771
17809
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17772
17810
  if (process.env.CI) return;
17773
17811
  if (!process.stdout.isTTY) return;
17774
- const current = true ? "2.60.63" : null;
17812
+ const current = true ? "2.60.65" : null;
17775
17813
  if (!current) return;
17776
17814
  const cache = readCache();
17777
17815
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17791,7 +17829,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
17791
17829
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
17792
17830
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
17793
17831
  function currentCliVersion() {
17794
- return true ? "2.60.63" : null;
17832
+ return true ? "2.60.65" : null;
17795
17833
  }
17796
17834
  function runCmd(cmd, args2, timeoutMs) {
17797
17835
  return new Promise((resolve7) => {
@@ -35220,7 +35258,7 @@ function checkChokidar() {
35220
35258
  }
35221
35259
  async function doctor(args2 = []) {
35222
35260
  const json = args2.includes("--json");
35223
- const cliVersion = true ? "2.60.63" : "0.0.0-dev";
35261
+ const cliVersion = true ? "2.60.65" : "0.0.0-dev";
35224
35262
  const apiBase2 = resolveApiBaseUrl();
35225
35263
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
35226
35264
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -35697,7 +35735,7 @@ async function mcpRun(args2) {
35697
35735
  // src/commands/version.ts
35698
35736
  var import_picocolors15 = __toESM(require("picocolors"));
35699
35737
  function version2() {
35700
- const v = true ? "2.60.63" : "unknown";
35738
+ const v = true ? "2.60.65" : "unknown";
35701
35739
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
35702
35740
  }
35703
35741
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.63",
3
+ "version": "2.60.65",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",