@ctliz/agent-intercom-claude 0.16.0 → 0.16.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-intercom",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Claude Code MCP plugin and wakeable worker for local Pi/Codex/Claude intercom messaging.",
5
5
  "author": {
6
6
  "name": "ctliz",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.1 - 2026-11-01
4
+
5
+ - Align `intercom_ask` soft-wait timeouts with Pi: defer timed-out asks on the broker (`ok: true`, `pending: true`, `deferred: true`) instead of cancelling them, so late `intercom_reply` answers still succeed; retain `cancelAsk` for explicit aborts.
6
+ - Align MCP tool `annotations` with Pi (`openWorldHint: false` for all local IPC tools and `destructiveHint: false` for `intercom_set_summary`, `intercom_pending`, and `intercom_reply`) so Claude Code auto-mode and companion Grok permission gates do not prompt unnecessarily.
7
+ - Document plugin-first `claude` interactive usage on Claude Code 2.1.316 alongside Channels and headless worker modes.
8
+
3
9
  ## 0.16.0 - 2026-10-11
4
10
 
5
11
  - Add opt-in `cci --tui --channel` wakeups through Claude Code Channels, sharing the existing MCP identity and unread inbox. Messages queued before initialization trigger a notice without a first user prompt.
package/README.md CHANGED
@@ -41,7 +41,7 @@ AGENT_INTERCOM_SCOPE_ID=<shared-team-or-workspace-scope>
41
41
 
42
42
  The plugin manifests intentionally do not contain static session IDs, because sharing one ID across concurrent panes would create identity collisions. `CLAUDE_INTERCOM_SESSION_ID` and `CLAUDE_INTERCOM_NAME` remain higher-priority compatibility aliases.
43
43
 
44
- Grok Build and AGY currently provide polling-only MCP integrations. Messages are durably retained, but there is no host-specific wake bridge to inject a new turn. Each active agent should call `intercom_pending` at startup and at natural work boundaries. They can join an existing team and exchange messages with every other adapter, but Agent Intercom Orchestrator does not currently spawn or lifecycle-manage Grok or AGY workers.
44
+ Grok Build supports native `monitor` and leader `_x.ai/interject` wakeups through `@ctliz/agent-intercom-grok`; AGY currently provides a polling-only MCP integration. Both join teams and exchange messages with every other adapter, while Agent Intercom Orchestrator does not currently spawn or lifecycle-manage Grok or AGY workers.
45
45
 
46
46
  ## Maintenance & Upstream Provenance
47
47
 
@@ -92,13 +92,12 @@ have it act with real system access.
92
92
 
93
93
  ## Status
94
94
 
95
- Preview. This is the Claude-side adapter, built alongside `pi-intercom` and
96
- `codex-intercom`.
95
+ Tested with Claude Code 2.1.316.
97
96
 
98
- A plain Claude Code MCP session does not receive unsolicited visible turns.
99
- Incoming messages are queued while the MCP server is running; call
100
- `intercom_pending` to read them. Wake-on-message workflows use `cci` /
101
- `claude-intercom-worker`.
97
+ - **Plugin mode (`claude` directly — Pi-consistent interactive workflow)**: Installing the packaged Claude Code plugin gives ordinary `claude` sessions automatic eager broker registration (`SessionStart`/`SessionEnd` hooks), native `/rename` presence synchronization, automatic inbound wakeups (`monitors/monitors.json`), and `/claude-intercom:intercom*` slash commands without a wrapper CLI.
98
+ - **Channel mode (`cci --tui --channel`)**: Uses Claude Code Channels (`notifications/claude/channel`) for cold-start wakeups before the first user prompt.
99
+ - **Headless worker mode (`cci` / `claude-intercom-worker`)**: Runs resumable headless `claude -p --resume` turns for background worker daemons and `agent-intercom-orchestrator`.
100
+ - **Bare MCP-only mode (`claude mcp add`)**: Without the plugin's hooks and monitor, incoming messages remain queued until read with `intercom_pending`.
102
101
 
103
102
  ## How Claude gets woken
104
103
 
@@ -102,7 +102,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
102
102
  name: "intercom_team",
103
103
  description: "Show all your named task teams, or inspect one by name. Falls back to managed-team discovery.",
104
104
  inputSchema: { type: "object", properties: { team: { type: "string" } }, additionalProperties: false },
105
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
105
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
106
106
  handler: async (args) => runtime.team(optionalString(args.team, "team")),
107
107
  },
108
108
  {
@@ -118,7 +118,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
118
118
  },
119
119
  additionalProperties: false,
120
120
  },
121
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
121
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
122
122
  handler: async (args) => runtime.join(
123
123
  optionalString(args.name, "name"),
124
124
  args.create === true,
@@ -130,7 +130,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
130
130
  name: "intercom_status",
131
131
  description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
132
132
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
133
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
133
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
134
134
  handler: async () => runtime.status(),
135
135
  },
136
136
  {
@@ -144,7 +144,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
144
144
  },
145
145
  additionalProperties: false,
146
146
  },
147
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
147
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
148
148
  handler: async (args) => runtime.list(
149
149
  args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
150
150
  asBoolean(args.include_self, false),
@@ -159,7 +159,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
159
159
  required: ["summary"],
160
160
  additionalProperties: false,
161
161
  },
162
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
162
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
163
163
  handler: async (args) => runtime.setSummary(asString(args.summary, "summary")),
164
164
  },
165
165
  {
@@ -176,7 +176,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
176
176
  required: ["to", "message"],
177
177
  additionalProperties: false,
178
178
  },
179
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
179
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
180
180
  handler: async (args) => runtime.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments), undefined, optionalString(args.team, "team")),
181
181
  },
182
182
  {
@@ -189,12 +189,12 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
189
189
  message: { type: "string" },
190
190
  attachments: attachmentsSchema,
191
191
  team: { type: "string", description: "Task team; required when multiple teams are shared." },
192
- timeout_ms: { type: "integer", minimum: 1, maximum: 120000, description: "Maximum time to wait for a reply before returning an error. Use intercom_send plus intercom_pending for longer work." },
192
+ timeout_ms: { type: "integer", minimum: 1, maximum: 120000, description: "Soft wait in milliseconds before continuing asynchronously (default 30000, max 120000); timed-out asks remain open for late replies." },
193
193
  },
194
194
  required: ["to", "message"],
195
195
  additionalProperties: false,
196
196
  },
197
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
197
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
198
198
  handler: async (args, signal) => runtime.ask(
199
199
  asString(args.to, "to"),
200
200
  asString(args.message, "message"),
@@ -212,7 +212,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
212
212
  properties: { mark_read: { type: "boolean", default: false } },
213
213
  additionalProperties: false,
214
214
  },
215
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
215
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
216
216
  handler: async (args) => runtime.pending(asBoolean(args.mark_read, false)),
217
217
  },
218
218
  {
@@ -231,7 +231,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
231
231
  required: ["message"],
232
232
  additionalProperties: false,
233
233
  },
234
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
234
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
235
235
  handler: async (args) => runtime.reply(asString(args.message, "message"), typeof args.to === "string" ? args.to : undefined, args.which === "oldest" || args.which === "latest" ? args.which : undefined, optionalString(args.askId, "askId"), optionalString(args.contextId, "contextId"), optionalString(args.team, "team")),
236
236
  },
237
237
  ];
package/claude/runtime.ts CHANGED
@@ -216,6 +216,19 @@ export interface ClaudeIntercomRuntimeOptions {
216
216
  reconnectDelays?: number[];
217
217
  }
218
218
 
219
+ class AskWaitElapsedError extends Error {
220
+ readonly from: string;
221
+ readonly replyTo: string;
222
+ readonly waitMs: number;
223
+ constructor(from: string, replyTo: string, waitMs: number) {
224
+ super(`No reply from "${from}" within ${Math.round(waitMs / 1000)} seconds`);
225
+ this.name = "AskWaitElapsedError";
226
+ this.from = from;
227
+ this.replyTo = replyTo;
228
+ this.waitMs = waitMs;
229
+ }
230
+ }
231
+
219
232
  export class ClaudeIntercomRuntime {
220
233
  private client: IntercomClient | null = null;
221
234
  private connectPromise: Promise<IntercomClient> | null = null;
@@ -474,9 +487,8 @@ export class ClaudeIntercomRuntime {
474
487
  };
475
488
  timeout = setTimeout(() => {
476
489
  this.replyWaiters.delete(replyTo);
477
- void this.client?.deferAsk(replyTo);
478
490
  signal?.removeEventListener("abort", onAbort);
479
- reject(new Error(`No reply from "${from}" within ${Math.round(timeoutMs / 1000)} seconds`));
491
+ reject(new AskWaitElapsedError(from, replyTo, timeoutMs));
480
492
  }, timeoutMs);
481
493
  signal?.addEventListener("abort", onAbort, { once: true });
482
494
  this.replyWaiters.set(replyTo, { from, replyTo, team, resolve, reject, timeout, cleanup });
@@ -597,6 +609,16 @@ export class ClaudeIntercomRuntime {
597
609
  const replyText = `${reply.content.text}${formatAttachments(reply.content.attachments)}`;
598
610
  return textResult(`Reply from ${to}:\n${replyText}`, { ok: true, message_id: result.id, reply });
599
611
  } catch (error) {
612
+ if (error instanceof AskWaitElapsedError) {
613
+ const deferred = await client.deferAsk(error.replyTo);
614
+ const seconds = Math.round(error.waitMs / 1000);
615
+ return textResult(
616
+ deferred
617
+ ? `No reply from "${to}" within ${seconds}s. The ask remains open asynchronously; continue only independent work and handle the late reply when it arrives.`
618
+ : `No reply from "${to}" within ${seconds}s; ask is no longer open.`,
619
+ { ok: true, pending: deferred, deferred, message_id: questionId, to, ...(team ? { team } : {}) },
620
+ );
621
+ }
600
622
  void client.cancelAsk(questionId);
601
623
  return textResult(error instanceof Error ? error.message : String(error), { ok: false }, true);
602
624
  }
@@ -2552,6 +2552,18 @@ function textResult(text, structuredContent, isError = false) {
2552
2552
  ...isError ? { isError: true } : {}
2553
2553
  };
2554
2554
  }
2555
+ var AskWaitElapsedError = class extends Error {
2556
+ from;
2557
+ replyTo;
2558
+ waitMs;
2559
+ constructor(from, replyTo, waitMs) {
2560
+ super(`No reply from "${from}" within ${Math.round(waitMs / 1e3)} seconds`);
2561
+ this.name = "AskWaitElapsedError";
2562
+ this.from = from;
2563
+ this.replyTo = replyTo;
2564
+ this.waitMs = waitMs;
2565
+ }
2566
+ };
2555
2567
  var ClaudeIntercomRuntime = class {
2556
2568
  client = null;
2557
2569
  connectPromise = null;
@@ -2789,9 +2801,8 @@ Other team memberships are unchanged.`, { ok: true, team: team.name, role: roste
2789
2801
  };
2790
2802
  timeout = setTimeout(() => {
2791
2803
  this.replyWaiters.delete(replyTo);
2792
- void this.client?.deferAsk(replyTo);
2793
2804
  signal?.removeEventListener("abort", onAbort);
2794
- reject(new Error(`No reply from "${from}" within ${Math.round(timeoutMs / 1e3)} seconds`));
2805
+ reject(new AskWaitElapsedError(from, replyTo, timeoutMs));
2795
2806
  }, timeoutMs);
2796
2807
  signal?.addEventListener("abort", onAbort, { once: true });
2797
2808
  this.replyWaiters.set(replyTo, { from, replyTo, team, resolve: resolve3, reject, timeout, cleanup });
@@ -2913,6 +2924,14 @@ Pending asks: ${this.unresolvedAsks.size}`,
2913
2924
  return textResult(`Reply from ${to}:
2914
2925
  ${replyText}`, { ok: true, message_id: result.id, reply });
2915
2926
  } catch (error2) {
2927
+ if (error2 instanceof AskWaitElapsedError) {
2928
+ const deferred = await client.deferAsk(error2.replyTo);
2929
+ const seconds = Math.round(error2.waitMs / 1e3);
2930
+ return textResult(
2931
+ deferred ? `No reply from "${to}" within ${seconds}s. The ask remains open asynchronously; continue only independent work and handle the late reply when it arrives.` : `No reply from "${to}" within ${seconds}s; ask is no longer open.`,
2932
+ { ok: true, pending: deferred, deferred, message_id: questionId, to, ...team ? { team } : {} }
2933
+ );
2934
+ }
2916
2935
  void client.cancelAsk(questionId);
2917
2936
  return textResult(error2 instanceof Error ? error2.message : String(error2), { ok: false }, true);
2918
2937
  }
@@ -3035,7 +3054,7 @@ function buildToolDefinitions(runtime) {
3035
3054
  name: "intercom_team",
3036
3055
  description: "Show all your named task teams, or inspect one by name. Falls back to managed-team discovery.",
3037
3056
  inputSchema: { type: "object", properties: { team: { type: "string" } }, additionalProperties: false },
3038
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
3057
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3039
3058
  handler: async (args) => runtime.team(optionalString(args.team, "team"))
3040
3059
  },
3041
3060
  {
@@ -3051,7 +3070,7 @@ function buildToolDefinitions(runtime) {
3051
3070
  },
3052
3071
  additionalProperties: false
3053
3072
  },
3054
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
3073
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
3055
3074
  handler: async (args) => runtime.join(
3056
3075
  optionalString(args.name, "name"),
3057
3076
  args.create === true,
@@ -3063,7 +3082,7 @@ function buildToolDefinitions(runtime) {
3063
3082
  name: "intercom_status",
3064
3083
  description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
3065
3084
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
3066
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
3085
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3067
3086
  handler: async () => runtime.status()
3068
3087
  },
3069
3088
  {
@@ -3077,7 +3096,7 @@ function buildToolDefinitions(runtime) {
3077
3096
  },
3078
3097
  additionalProperties: false
3079
3098
  },
3080
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
3099
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3081
3100
  handler: async (args) => runtime.list(
3082
3101
  args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
3083
3102
  asBoolean(args.include_self, false)
@@ -3092,7 +3111,7 @@ function buildToolDefinitions(runtime) {
3092
3111
  required: ["summary"],
3093
3112
  additionalProperties: false
3094
3113
  },
3095
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
3114
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3096
3115
  handler: async (args) => runtime.setSummary(asString(args.summary, "summary"))
3097
3116
  },
3098
3117
  {
@@ -3109,7 +3128,7 @@ function buildToolDefinitions(runtime) {
3109
3128
  required: ["to", "message"],
3110
3129
  additionalProperties: false
3111
3130
  },
3112
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
3131
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
3113
3132
  handler: async (args) => runtime.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments), void 0, optionalString(args.team, "team"))
3114
3133
  },
3115
3134
  {
@@ -3122,12 +3141,12 @@ function buildToolDefinitions(runtime) {
3122
3141
  message: { type: "string" },
3123
3142
  attachments: attachmentsSchema,
3124
3143
  team: { type: "string", description: "Task team; required when multiple teams are shared." },
3125
- timeout_ms: { type: "integer", minimum: 1, maximum: 12e4, description: "Maximum time to wait for a reply before returning an error. Use intercom_send plus intercom_pending for longer work." }
3144
+ timeout_ms: { type: "integer", minimum: 1, maximum: 12e4, description: "Soft wait in milliseconds before continuing asynchronously (default 30000, max 120000); timed-out asks remain open for late replies." }
3126
3145
  },
3127
3146
  required: ["to", "message"],
3128
3147
  additionalProperties: false
3129
3148
  },
3130
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
3149
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
3131
3150
  handler: async (args, signal) => runtime.ask(
3132
3151
  asString(args.to, "to"),
3133
3152
  asString(args.message, "message"),
@@ -3145,7 +3164,7 @@ function buildToolDefinitions(runtime) {
3145
3164
  properties: { mark_read: { type: "boolean", default: false } },
3146
3165
  additionalProperties: false
3147
3166
  },
3148
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
3167
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3149
3168
  handler: async (args) => runtime.pending(asBoolean(args.mark_read, false))
3150
3169
  },
3151
3170
  {
@@ -3164,7 +3183,7 @@ function buildToolDefinitions(runtime) {
3164
3183
  required: ["message"],
3165
3184
  additionalProperties: false
3166
3185
  },
3167
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
3186
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
3168
3187
  handler: async (args) => runtime.reply(asString(args.message, "message"), typeof args.to === "string" ? args.to : void 0, args.which === "oldest" || args.which === "latest" ? args.which : void 0, optionalString(args.askId, "askId"), optionalString(args.contextId, "contextId"), optionalString(args.team, "team"))
3169
3188
  }
3170
3189
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctliz/agent-intercom-claude",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Claude Code MCP plugin and wakeable worker for local messaging with Pi, Codex, OpenCode, Grok Build, and AGY agents.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",