@ctliz/agent-intercom-claude 0.12.0-connect.3 → 0.13.0-connect.3
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 +10 -0
- package/README.md +40 -0
- package/broker/spawn.ts +2 -0
- package/claude/cci.ts +2 -0
- package/claude/cli-runner.ts +1 -0
- package/claude/contact.ts +3 -3
- package/claude/mcp-protocol.ts +16 -1
- package/claude/native-protocol.ts +1 -1
- package/claude/runtime.ts +19 -3
- package/claude/team.ts +210 -31
- package/claude/transport.ts +1 -0
- package/dist/cci.mjs +14 -8
- package/dist/ccim.mjs +14 -8
- package/dist/claude-server.mjs +162 -28
- package/dist/worker-daemon.mjs +10 -6
- package/package.json +3 -3
- package/scripts/verify-core-provenance.mjs +17 -15
- package/scripts/verify-packed-runtime.mjs +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0-connect.3 - 2026-08-23
|
|
4
|
+
|
|
5
|
+
- Add complete OpenAI MCP annotations for all nine Intercom tools and verify their exact semantics in tests.
|
|
6
|
+
- Reference every MCP tool by name so tool coverage is complete.
|
|
7
|
+
- Explicitly disable shell execution for production and release-verification child processes.
|
|
8
|
+
|
|
9
|
+
## 0.13.0-connect.2 - 2026-08-18
|
|
10
|
+
|
|
11
|
+
- Document standard stdio MCP setup for Grok Build and AGY, including host-neutral identity variables, scope requirements, source-checkout configuration, manual `intercom_pending` polling, and the absence of wake-on-message support.
|
|
12
|
+
|
|
3
13
|
## 0.12.0-connect.3 - 2026-08-14
|
|
4
14
|
|
|
5
15
|
- Fix package bundling by adding `monitors/**/*` (`monitors/monitors.json`) to `package.json` `files`, ensuring Claude Code plugin monitor configurations resolve properly at runtime when installed from the packaged npm artifact.
|
package/README.md
CHANGED
|
@@ -149,6 +149,44 @@ claude mcp add claude-intercom -- claude-intercom-mcp
|
|
|
149
149
|
|
|
150
150
|
With `--transport mcp`, `cci` does this automatically for each normal headless worker. Native headless workers are still woken and replied through the worker daemon's broker connection, but omit the packaged MCP server from the Claude turn. `ccim` intentionally uses Claude's `--safe-mode`, which disables MCP servers along with plugins, hooks, and skills.
|
|
151
151
|
|
|
152
|
+
### Other stdio MCP hosts
|
|
153
|
+
|
|
154
|
+
The `claude-intercom-mcp` executable is a standard stdio MCP server, so Grok Build, AGY, and other compatible local hosts can use it without installing the Claude Code plugin. An npm/global installation provides that executable; for a source checkout, configure `node` with the absolute path to `dist/claude-server.mjs`. Node 20+ is required.
|
|
155
|
+
|
|
156
|
+
Use `AGENT_INTERCOM_SESSION_ID` and `AGENT_INTERCOM_SESSION_NAME` for a host-neutral identity; `CLAUDE_INTERCOM_SESSION_ID` and `CLAUDE_INTERCOM_NAME` remain higher-priority compatibility aliases. Do not share a fixed session ID between concurrent host sessions.
|
|
157
|
+
|
|
158
|
+
A plain MCP host is connected only while its MCP process is alive. Incoming Intercom messages are durable and appear through `intercom_pending`, but this server cannot itself inject a new prompt into Grok or AGY. Use `intercom_send` plus `intercom_pending` for manual-polling workflows; a host-specific relay is required for wake-on-message.
|
|
159
|
+
|
|
160
|
+
For Grok Build, register the server and use a unique static ID for an individual long-lived probe or worker:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
grok mcp add agent-intercom \
|
|
164
|
+
-e AGENT_INTERCOM_SESSION_ID=grok-probe-01 \
|
|
165
|
+
-e AGENT_INTERCOM_SESSION_NAME=grok-probe \
|
|
166
|
+
-e CLAUDE_INTERCOM_MODEL=grok-build \
|
|
167
|
+
-- claude-intercom-mcp
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For AGY, add the equivalent entry to `~/.gemini/config/mcp_config.json` (replace the source-checkout paths when using an installed `claude-intercom-mcp` executable):
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"mcpServers": {
|
|
175
|
+
"agent-intercom": {
|
|
176
|
+
"command": "/absolute/path/to/node",
|
|
177
|
+
"args": ["/absolute/path/to/agent-intercom-claude/dist/claude-server.mjs"],
|
|
178
|
+
"env": {
|
|
179
|
+
"AGENT_INTERCOM_SESSION_ID": "agy-probe-01",
|
|
180
|
+
"AGENT_INTERCOM_SESSION_NAME": "agy-probe",
|
|
181
|
+
"CLAUDE_INTERCOM_MODEL": "agy"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Both hosts must use the same `AGENT_INTERCOM_SCOPE_ID` as their intended peers, or leave it unset for the default local scope. Call `intercom_whoami` once after startup, then use `intercom_list`, `intercom_send`, and `intercom_pending` to verify delivery.
|
|
189
|
+
|
|
152
190
|
Optional identity variables can be attached at registration time:
|
|
153
191
|
|
|
154
192
|
```bash
|
|
@@ -413,6 +451,8 @@ environment when no config file is given (`CLAUDE_INTERCOM_WORKER_ID`, `…_NAME
|
|
|
413
451
|
| `CLAUDE_INTERCOM_NAME` | MCP server | Discoverable session name |
|
|
414
452
|
| `CLAUDE_INTERCOM_SESSION_ID` | MCP server | Stable intercom id |
|
|
415
453
|
| `CLAUDE_INTERCOM_MODEL` | MCP server | Model label shown to peers |
|
|
454
|
+
| `AGENT_INTERCOM_SESSION_ID` / `_NAME` | MCP server | Host-neutral identity fallback; Claude-specific values take precedence |
|
|
455
|
+
| `AGENT_INTERCOM_SCOPE_ID` | MCP server | Broker-enforced local routing scope |
|
|
416
456
|
| `CLAUDE_INTERCOM_EFFORT` | `cci` / `ccim` | Effort level forwarded to every Claude turn |
|
|
417
457
|
| `CLAUDE_INTERCOM_CWD` / `_INSTRUCTIONS` | `cci` / `ccim` | Defaults for `--cwd` / `--instructions` |
|
|
418
458
|
| `CLAUDE_INTERCOM_CLAUDE_COMMAND` | workers | Claude Code executable (default `claude`) |
|
package/broker/spawn.ts
CHANGED
|
@@ -191,6 +191,7 @@ export function getBrokerSpawnOptions(
|
|
|
191
191
|
cwd: string;
|
|
192
192
|
env: NodeJS.ProcessEnv;
|
|
193
193
|
windowsHide: true;
|
|
194
|
+
shell: false;
|
|
194
195
|
} {
|
|
195
196
|
return {
|
|
196
197
|
detached: true,
|
|
@@ -198,6 +199,7 @@ export function getBrokerSpawnOptions(
|
|
|
198
199
|
cwd: extensionDir,
|
|
199
200
|
env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
200
201
|
windowsHide: true,
|
|
202
|
+
shell: false,
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
205
|
|
package/claude/cci.ts
CHANGED
|
@@ -66,6 +66,7 @@ function gitString(cwd: string, args: string[]): string | null {
|
|
|
66
66
|
cwd,
|
|
67
67
|
encoding: "utf8",
|
|
68
68
|
stdio: ["ignore", "pipe", "ignore"],
|
|
69
|
+
shell: false,
|
|
69
70
|
});
|
|
70
71
|
if (result.status !== 0) return null;
|
|
71
72
|
const trimmed = result.stdout.trim();
|
|
@@ -341,6 +342,7 @@ async function runCciTui(options: CciOptions, id: string, name: string, scopeId:
|
|
|
341
342
|
const child = spawn(options.claudeCommand, args, {
|
|
342
343
|
cwd: options.cwd,
|
|
343
344
|
stdio: "inherit",
|
|
345
|
+
shell: false,
|
|
344
346
|
env: {
|
|
345
347
|
...process.env,
|
|
346
348
|
...scopeEnv,
|
package/claude/cli-runner.ts
CHANGED
|
@@ -116,6 +116,7 @@ export function runClaudeTurn(options: ClaudeTurnOptions): Promise<ClaudeTurnRes
|
|
|
116
116
|
cwd: launch.cwd,
|
|
117
117
|
env: { ...process.env, ...options.env },
|
|
118
118
|
stdio: ["pipe", "pipe", "pipe"],
|
|
119
|
+
shell: false,
|
|
119
120
|
});
|
|
120
121
|
} finally {
|
|
121
122
|
// Directory descriptors remain open across the synchronous spawn setup,
|
package/claude/contact.ts
CHANGED
|
@@ -20,14 +20,14 @@ export function copyTextToClipboard(text: string): boolean {
|
|
|
20
20
|
["clip.exe", []],
|
|
21
21
|
];
|
|
22
22
|
for (const [command, args] of candidates) {
|
|
23
|
-
if (spawnSync("which", [command], { stdio: "ignore" }).status !== 0) continue;
|
|
23
|
+
if (spawnSync("which", [command], { stdio: "ignore", shell: false }).status !== 0) continue;
|
|
24
24
|
if (command === "wl-copy") {
|
|
25
|
-
const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
25
|
+
const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"], shell: false });
|
|
26
26
|
child.stdin.end(text);
|
|
27
27
|
child.unref();
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
|
-
if (spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] }).status === 0) return true;
|
|
30
|
+
if (spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"], shell: false }).status === 0) return true;
|
|
31
31
|
}
|
|
32
32
|
return false;
|
|
33
33
|
}
|
package/claude/mcp-protocol.ts
CHANGED
|
@@ -17,6 +17,12 @@ interface ToolDefinition {
|
|
|
17
17
|
name: string;
|
|
18
18
|
description: string;
|
|
19
19
|
inputSchema: Record<string, unknown>;
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: boolean;
|
|
22
|
+
destructiveHint: boolean;
|
|
23
|
+
idempotentHint: boolean;
|
|
24
|
+
openWorldHint: boolean;
|
|
25
|
+
};
|
|
20
26
|
handler: ToolHandler;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -78,18 +84,21 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
78
84
|
name: "intercom_whoami",
|
|
79
85
|
description: "Return this Claude session's intercom identity for reliable targeting.",
|
|
80
86
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
87
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
81
88
|
handler: async () => runtime.whoami(),
|
|
82
89
|
},
|
|
83
90
|
{
|
|
84
91
|
name: "intercom_team",
|
|
85
92
|
description: "Show your current manager and the live coworkers owned by that manager. No arguments are required.",
|
|
86
93
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
94
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
87
95
|
handler: async () => runtime.team(),
|
|
88
96
|
},
|
|
89
97
|
{
|
|
90
98
|
name: "intercom_status",
|
|
91
99
|
description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
|
|
92
100
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
101
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
93
102
|
handler: async () => runtime.status(),
|
|
94
103
|
},
|
|
95
104
|
{
|
|
@@ -103,6 +112,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
103
112
|
},
|
|
104
113
|
additionalProperties: false,
|
|
105
114
|
},
|
|
115
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
106
116
|
handler: async (args) => runtime.list(
|
|
107
117
|
args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
|
|
108
118
|
asBoolean(args.include_self, false),
|
|
@@ -117,6 +127,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
117
127
|
required: ["summary"],
|
|
118
128
|
additionalProperties: false,
|
|
119
129
|
},
|
|
130
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
120
131
|
handler: async (args) => runtime.setSummary(asString(args.summary, "summary")),
|
|
121
132
|
},
|
|
122
133
|
{
|
|
@@ -132,6 +143,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
132
143
|
required: ["to", "message"],
|
|
133
144
|
additionalProperties: false,
|
|
134
145
|
},
|
|
146
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
135
147
|
handler: async (args) => runtime.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments)),
|
|
136
148
|
},
|
|
137
149
|
{
|
|
@@ -148,6 +160,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
148
160
|
required: ["to", "message"],
|
|
149
161
|
additionalProperties: false,
|
|
150
162
|
},
|
|
163
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
151
164
|
handler: async (args, signal) => runtime.ask(
|
|
152
165
|
asString(args.to, "to"),
|
|
153
166
|
asString(args.message, "message"),
|
|
@@ -164,6 +177,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
164
177
|
properties: { mark_read: { type: "boolean", default: false } },
|
|
165
178
|
additionalProperties: false,
|
|
166
179
|
},
|
|
180
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
167
181
|
handler: async (args) => runtime.pending(asBoolean(args.mark_read, false)),
|
|
168
182
|
},
|
|
169
183
|
{
|
|
@@ -179,6 +193,7 @@ export function buildToolDefinitions(runtime: ClaudeIntercomRuntime): ToolDefini
|
|
|
179
193
|
required: ["message"],
|
|
180
194
|
additionalProperties: false,
|
|
181
195
|
},
|
|
196
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
182
197
|
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),
|
|
183
198
|
},
|
|
184
199
|
];
|
|
@@ -222,7 +237,7 @@ export async function handleMcpRequest(request: JsonRpcRequest, runtime: ClaudeI
|
|
|
222
237
|
return ok(request.id, {});
|
|
223
238
|
case "tools/list":
|
|
224
239
|
return ok(request.id, {
|
|
225
|
-
tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })),
|
|
240
|
+
tools: tools.map(({ name, description, inputSchema, annotations }) => ({ name, description, inputSchema, annotations })),
|
|
226
241
|
});
|
|
227
242
|
case "tools/call": {
|
|
228
243
|
const name = request.params?.name;
|
|
@@ -271,7 +271,7 @@ export async function bindNativeClaudeSocket(socketPath: string, onFrame: (frame
|
|
|
271
271
|
|
|
272
272
|
function processStart(pid: number): Promise<string | undefined> {
|
|
273
273
|
return new Promise((resolve) => {
|
|
274
|
-
execFile("ps", ["-o", "lstart=", "-p", String(pid)], (error, stdout) => resolve(error ? undefined : stdout.trim() || undefined));
|
|
274
|
+
execFile("ps", ["-o", "lstart=", "-p", String(pid)], { shell: false }, (error, stdout) => resolve(error ? undefined : stdout.trim() || undefined));
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
|
package/claude/runtime.ts
CHANGED
|
@@ -96,13 +96,28 @@ function shortHash(value: string): string {
|
|
|
96
96
|
return createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
const GENERIC_SESSION_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
|
|
100
|
+
|
|
99
101
|
export function buildClaudeRuntimeIdentity(env: NodeJS.ProcessEnv = process.env, cwd = env.PWD || processCwd(), pid = process.pid): ClaudeRuntimeIdentity {
|
|
100
|
-
|
|
101
|
-
|| env.CLAUDE_PEER_ID?.trim()
|
|
102
|
-
|
|
102
|
+
let sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim()
|
|
103
|
+
|| env.CLAUDE_PEER_ID?.trim();
|
|
104
|
+
if (!sessionId && env.AGENT_INTERCOM_SESSION_ID !== undefined) {
|
|
105
|
+
const raw = env.AGENT_INTERCOM_SESSION_ID.trim();
|
|
106
|
+
if (raw) {
|
|
107
|
+
if (!GENERIC_SESSION_ID_REGEX.test(raw)) {
|
|
108
|
+
throw new Error("Invalid AGENT_INTERCOM_SESSION_ID: must match ^[A-Za-z0-9_-]{1,128}$");
|
|
109
|
+
}
|
|
110
|
+
sessionId = raw;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!sessionId) {
|
|
114
|
+
sessionId = `claude-${pid}-${shortHash(cwd)}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
const cwdName = basename(cwd) || "workspace";
|
|
104
118
|
const name = env.CLAUDE_INTERCOM_NAME?.trim()
|
|
105
119
|
|| env.CLAUDE_PEER_NAME?.trim()
|
|
120
|
+
|| (env.AGENT_INTERCOM_SESSION_NAME !== undefined && env.AGENT_INTERCOM_SESSION_NAME.trim() ? env.AGENT_INTERCOM_SESSION_NAME.trim() : undefined)
|
|
106
121
|
|| `claude-${cwdName}-${pid}`;
|
|
107
122
|
return {
|
|
108
123
|
sessionId,
|
|
@@ -168,6 +183,7 @@ export function detectGitRoot(cwd: string): string | null {
|
|
|
168
183
|
cwd,
|
|
169
184
|
encoding: "utf8",
|
|
170
185
|
stdio: ["ignore", "pipe", "ignore"],
|
|
186
|
+
shell: false,
|
|
171
187
|
});
|
|
172
188
|
if (result.status !== 0) return null;
|
|
173
189
|
return result.stdout.trim() || null;
|
package/claude/team.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
|
|
4
|
+
import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
|
|
4
5
|
import type { BossSessionMetadata } from "../types.ts";
|
|
5
6
|
import { getAgentDirPath } from "../broker/paths.ts";
|
|
6
7
|
|
|
8
|
+
export type TeamSource = "orchestrator" | "boss" | "manifest" | "live" | "standalone";
|
|
9
|
+
|
|
7
10
|
export interface TeamSession {
|
|
8
11
|
id: string;
|
|
9
12
|
name?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
origin?: "local" | "remote";
|
|
10
15
|
/** Already parsed and broker-authenticated by the client session decoder. */
|
|
11
16
|
boss?: Pick<BossSessionMetadata, "binding" | "principal" | "liveWorker">;
|
|
12
17
|
}
|
|
@@ -42,6 +47,7 @@ export interface IntercomTeam {
|
|
|
42
47
|
self: { id: string; workerId?: string; isManager: boolean };
|
|
43
48
|
manager?: { target: string; connected: boolean };
|
|
44
49
|
coworkers: TeamMember[];
|
|
50
|
+
source: TeamSource;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
const LEGACY_LIVE_STATES = new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
|
|
@@ -212,11 +218,106 @@ function teamMember(worker: StoredWorker, session: TeamSession): TeamMember {
|
|
|
212
218
|
};
|
|
213
219
|
}
|
|
214
220
|
|
|
215
|
-
function
|
|
221
|
+
async function resolveNonAuthoritativeTeam(
|
|
222
|
+
input: { selfId: string; sessions: TeamSession[] },
|
|
223
|
+
env: NodeJS.ProcessEnv,
|
|
224
|
+
): Promise<IntercomTeam> {
|
|
225
|
+
if (env.AGENT_INTERCOM_TEAM_MANIFEST !== undefined) {
|
|
226
|
+
const rawPath = env.AGENT_INTERCOM_TEAM_MANIFEST.trim();
|
|
227
|
+
if (!rawPath) {
|
|
228
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
229
|
+
}
|
|
230
|
+
const manifest = await readTeamManifestAsync(rawPath);
|
|
231
|
+
if (!manifest) {
|
|
232
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
233
|
+
}
|
|
234
|
+
const selfMember = manifest.members.find((m) => m.sessionId === input.selfId);
|
|
235
|
+
if (!selfMember) {
|
|
236
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const isManager = input.selfId === manifest.leadId;
|
|
240
|
+
const managerTarget = manifest.leadId;
|
|
241
|
+
const managerConnected = connectedTo(input.sessions, managerTarget);
|
|
242
|
+
|
|
243
|
+
const coworkers: TeamMember[] = manifest.members
|
|
244
|
+
.filter((m) => m.sessionId !== input.selfId)
|
|
245
|
+
.filter((m) => isManager || m.sessionId !== managerTarget)
|
|
246
|
+
.map((m) => {
|
|
247
|
+
const live = input.sessions.find((s) => s.id === m.sessionId || s.name === m.sessionId);
|
|
248
|
+
return {
|
|
249
|
+
id: m.sessionId,
|
|
250
|
+
target: m.sessionId,
|
|
251
|
+
role: m.role,
|
|
252
|
+
connected: Boolean(live),
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
teamId: manifest.runId,
|
|
258
|
+
self: { id: input.selfId, isManager },
|
|
259
|
+
manager: { target: managerTarget, connected: managerConnected },
|
|
260
|
+
coworkers,
|
|
261
|
+
source: "manifest",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (stringValue(env.AGENT_INTERCOM_SCOPE_ID)) {
|
|
266
|
+
const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
|
|
267
|
+
?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
268
|
+
const role = stringValue(env.AGENT_INTERCOM_ROLE)?.toLowerCase();
|
|
269
|
+
|
|
270
|
+
const isManager = role === "manager"
|
|
271
|
+
|| (managerTarget !== undefined && managerTarget === input.selfId)
|
|
272
|
+
|| (managerTarget === undefined && role !== "worker");
|
|
273
|
+
|
|
274
|
+
const effectiveManagerTarget = isManager ? input.selfId : managerTarget;
|
|
275
|
+
|
|
276
|
+
const coworkers: TeamMember[] = input.sessions
|
|
277
|
+
.filter((session) => session.id !== input.selfId)
|
|
278
|
+
.filter((session) => session.model !== "human")
|
|
279
|
+
.filter((session) => session.id !== effectiveManagerTarget)
|
|
280
|
+
.map((session): TeamMember => ({
|
|
281
|
+
id: session.id,
|
|
282
|
+
target: session.id,
|
|
283
|
+
...(session.model ? { harness: session.model } : {}),
|
|
284
|
+
connected: true,
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
const manager = effectiveManagerTarget
|
|
288
|
+
? {
|
|
289
|
+
target: effectiveManagerTarget,
|
|
290
|
+
connected: isManager ? true : connectedTo(input.sessions, effectiveManagerTarget),
|
|
291
|
+
}
|
|
292
|
+
: undefined;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
teamId: effectiveManagerTarget ?? input.selfId,
|
|
296
|
+
self: { id: input.selfId, isManager },
|
|
297
|
+
...(manager ? { manager } : {}),
|
|
298
|
+
coworkers,
|
|
299
|
+
source: "live",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
|
|
304
|
+
?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
305
|
+
return {
|
|
306
|
+
teamId: managerTarget ?? input.selfId,
|
|
307
|
+
self: { id: input.selfId, isManager: !managerTarget },
|
|
308
|
+
manager: managerTarget
|
|
309
|
+
? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) }
|
|
310
|
+
: { target: input.selfId, connected: true },
|
|
311
|
+
coworkers: [],
|
|
312
|
+
source: "standalone",
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function resolveLegacyTeam(
|
|
216
317
|
input: { selfId: string; sessions: TeamSession[] },
|
|
217
318
|
workers: StoredWorker[],
|
|
218
319
|
env: NodeJS.ProcessEnv,
|
|
219
|
-
): IntercomTeam {
|
|
320
|
+
): Promise<IntercomTeam> {
|
|
220
321
|
const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
|
|
221
322
|
const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
|
|
222
323
|
const current = workerId
|
|
@@ -225,37 +326,86 @@ function resolveLegacyTeam(
|
|
|
225
326
|
&& (!runId || stringValue(worker.runId) === runId)
|
|
226
327
|
)
|
|
227
328
|
: undefined;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
.filter((worker) => stringValue(worker.managerSessionId) === teamId)
|
|
235
|
-
.filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? ""))
|
|
236
|
-
.filter((worker) => stringValue(worker.id) !== workerId)
|
|
237
|
-
.map((worker): TeamMember | undefined => {
|
|
238
|
-
const id = stringValue(worker.id);
|
|
239
|
-
if (!id) return undefined;
|
|
240
|
-
const target = stringValue(worker.intercomTarget) ?? id;
|
|
329
|
+
|
|
330
|
+
if (current) {
|
|
331
|
+
const managerTarget = stringValue(current?.managerSessionId)
|
|
332
|
+
?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
|
|
333
|
+
?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
334
|
+
if (!managerTarget) {
|
|
241
335
|
return {
|
|
242
|
-
id,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
...(stringValue(worker.role) ? { role: stringValue(worker.role) } : {}),
|
|
246
|
-
...(stringValue(worker.state) ? { state: stringValue(worker.state) } : {}),
|
|
247
|
-
connected: connectedTo(input.sessions, target),
|
|
336
|
+
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
|
|
337
|
+
coworkers: [],
|
|
338
|
+
source: "orchestrator",
|
|
248
339
|
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
340
|
+
}
|
|
341
|
+
const coworkers = workers
|
|
342
|
+
.filter((worker) => worker.owned === true)
|
|
343
|
+
.filter((worker) => {
|
|
344
|
+
const mgr = stringValue(worker.managerSessionId);
|
|
345
|
+
return mgr !== undefined && mgr === managerTarget;
|
|
346
|
+
})
|
|
347
|
+
.filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? ""))
|
|
348
|
+
.filter((worker) => stringValue(worker.id) !== workerId && stringValue(worker.id) !== input.selfId)
|
|
349
|
+
.map((worker): TeamMember | undefined => {
|
|
350
|
+
const id = stringValue(worker.id);
|
|
351
|
+
if (!id) return undefined;
|
|
352
|
+
const target = stringValue(worker.intercomTarget) ?? id;
|
|
353
|
+
return {
|
|
354
|
+
id,
|
|
355
|
+
target,
|
|
356
|
+
...(stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {}),
|
|
357
|
+
...(stringValue(worker.role) ? { role: stringValue(worker.role) } : {}),
|
|
358
|
+
...(stringValue(worker.state) ? { state: stringValue(worker.state) } : {}),
|
|
359
|
+
connected: connectedTo(input.sessions, target),
|
|
360
|
+
};
|
|
361
|
+
})
|
|
362
|
+
.filter((member): member is TeamMember => Boolean(member));
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
teamId: managerTarget,
|
|
366
|
+
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
|
|
367
|
+
manager: { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) },
|
|
368
|
+
coworkers,
|
|
369
|
+
source: "orchestrator",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (workerId === undefined) {
|
|
374
|
+
const ownedCoworkers = workers
|
|
375
|
+
.filter((worker) => worker.owned === true)
|
|
376
|
+
.filter((worker) => {
|
|
377
|
+
const mgr = stringValue(worker.managerSessionId);
|
|
378
|
+
return mgr !== undefined && mgr === input.selfId;
|
|
379
|
+
})
|
|
380
|
+
.filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? ""))
|
|
381
|
+
.filter((worker) => stringValue(worker.id) !== input.selfId)
|
|
382
|
+
.map((worker): TeamMember | undefined => {
|
|
383
|
+
const id = stringValue(worker.id);
|
|
384
|
+
if (!id) return undefined;
|
|
385
|
+
const target = stringValue(worker.intercomTarget) ?? id;
|
|
386
|
+
return {
|
|
387
|
+
id,
|
|
388
|
+
target,
|
|
389
|
+
...(stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {}),
|
|
390
|
+
...(stringValue(worker.role) ? { role: stringValue(worker.role) } : {}),
|
|
391
|
+
...(stringValue(worker.state) ? { state: stringValue(worker.state) } : {}),
|
|
392
|
+
connected: connectedTo(input.sessions, target),
|
|
393
|
+
};
|
|
394
|
+
})
|
|
395
|
+
.filter((member): member is TeamMember => Boolean(member));
|
|
396
|
+
|
|
397
|
+
if (ownedCoworkers.length > 0) {
|
|
398
|
+
return {
|
|
399
|
+
teamId: input.selfId,
|
|
400
|
+
self: { id: input.selfId, isManager: true },
|
|
401
|
+
manager: { target: input.selfId, connected: true },
|
|
402
|
+
coworkers: ownedCoworkers,
|
|
403
|
+
source: "orchestrator",
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return resolveNonAuthoritativeTeam(input, env);
|
|
259
409
|
}
|
|
260
410
|
|
|
261
411
|
export async function resolveIntercomTeam(input: {
|
|
@@ -276,6 +426,7 @@ export async function resolveIntercomTeam(input: {
|
|
|
276
426
|
teamId: selfSession.boss.binding.bossRunId,
|
|
277
427
|
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
|
|
278
428
|
coworkers: [],
|
|
429
|
+
source: "boss",
|
|
279
430
|
};
|
|
280
431
|
}
|
|
281
432
|
|
|
@@ -301,6 +452,7 @@ export async function resolveIntercomTeam(input: {
|
|
|
301
452
|
teamId: selfSession.boss.binding.bossRunId,
|
|
302
453
|
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
|
|
303
454
|
coworkers: [],
|
|
455
|
+
source: "boss",
|
|
304
456
|
};
|
|
305
457
|
}
|
|
306
458
|
|
|
@@ -327,9 +479,36 @@ export async function resolveIntercomTeam(input: {
|
|
|
327
479
|
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: selfIsManager },
|
|
328
480
|
manager: { target: managerTarget, connected: true },
|
|
329
481
|
coworkers,
|
|
482
|
+
source: "boss",
|
|
330
483
|
};
|
|
331
484
|
}
|
|
332
485
|
|
|
486
|
+
/** Authorizes a read-only local inbox lookup using exact orchestrator ownership. */
|
|
487
|
+
export function resolveManagedInboxSession(input: {
|
|
488
|
+
team: IntercomTeam;
|
|
489
|
+
sessions: TeamSession[];
|
|
490
|
+
requestedSession: string;
|
|
491
|
+
}): TeamSession {
|
|
492
|
+
if (input.team.source !== "orchestrator" && input.team.source !== "boss") {
|
|
493
|
+
throw new Error("Pending-ask inbox access denied: cross-session inbox inspection is only permitted for Orchestrator/Boss-managed teams");
|
|
494
|
+
}
|
|
495
|
+
if (!input.team.self.isManager) {
|
|
496
|
+
throw new Error("Only a manager may inspect another session's pending-ask inbox");
|
|
497
|
+
}
|
|
498
|
+
const member = input.team.coworkers.find((entry) => entry.target === input.requestedSession);
|
|
499
|
+
if (!member) {
|
|
500
|
+
throw new Error(`Pending-ask inbox access denied for "${input.requestedSession}"; select an owned coworker target returned by intercom_team`);
|
|
501
|
+
}
|
|
502
|
+
const liveSession = input.sessions.find((session) => session.id === input.requestedSession);
|
|
503
|
+
if (!liveSession) {
|
|
504
|
+
throw new Error(`Pending-ask inbox access denied for "${input.requestedSession}"; the owned coworker target must equal an exact connected stable session ID`);
|
|
505
|
+
}
|
|
506
|
+
if (liveSession.origin === "remote") {
|
|
507
|
+
throw new Error(`Pending-ask inbox "${input.requestedSession}" is remote and cannot be read from this host`);
|
|
508
|
+
}
|
|
509
|
+
return liveSession;
|
|
510
|
+
}
|
|
511
|
+
|
|
333
512
|
export function formatIntercomTeam(team: IntercomTeam): string {
|
|
334
513
|
const lines = [
|
|
335
514
|
`Manager: ${team.manager ? `${team.manager.target} [${team.manager.connected ? "connected" : "not connected"}]` : "unknown"}`,
|
package/claude/transport.ts
CHANGED
|
@@ -49,6 +49,7 @@ export function probeClaudeVersion(command = "claude"): ClaudeVersionProbe {
|
|
|
49
49
|
const result = spawnSync(command, ["--version"], {
|
|
50
50
|
encoding: "utf8",
|
|
51
51
|
stdio: ["ignore", "pipe", "pipe"],
|
|
52
|
+
shell: false,
|
|
52
53
|
});
|
|
53
54
|
if (result.error || result.status !== 0) {
|
|
54
55
|
return {
|
package/dist/cci.mjs
CHANGED
|
@@ -274,7 +274,8 @@ function runClaudeTurn(options) {
|
|
|
274
274
|
child = spawn(command, launch.args, {
|
|
275
275
|
cwd: launch.cwd,
|
|
276
276
|
env: { ...process.env, ...options.env },
|
|
277
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
277
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
278
|
+
shell: false
|
|
278
279
|
});
|
|
279
280
|
} finally {
|
|
280
281
|
launch.release();
|
|
@@ -566,7 +567,8 @@ function isNativeClaudeVersionCompatible(version) {
|
|
|
566
567
|
function probeClaudeVersion(command = "claude") {
|
|
567
568
|
const result = spawnSync(command, ["--version"], {
|
|
568
569
|
encoding: "utf8",
|
|
569
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
570
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
571
|
+
shell: false
|
|
570
572
|
});
|
|
571
573
|
if (result.error || result.status !== 0) {
|
|
572
574
|
return {
|
|
@@ -2063,7 +2065,8 @@ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env)
|
|
|
2063
2065
|
stdio: "ignore",
|
|
2064
2066
|
cwd: extensionDir,
|
|
2065
2067
|
env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
2066
|
-
windowsHide: true
|
|
2068
|
+
windowsHide: true,
|
|
2069
|
+
shell: false
|
|
2067
2070
|
};
|
|
2068
2071
|
}
|
|
2069
2072
|
function toError2(error) {
|
|
@@ -2399,6 +2402,7 @@ function defaultInboxPath(sessionId, intercomDir = getIntercomDirPath()) {
|
|
|
2399
2402
|
|
|
2400
2403
|
// claude/team.ts
|
|
2401
2404
|
import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
|
|
2405
|
+
import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
|
|
2402
2406
|
var BOSS_NONTERMINAL_STATES = new Set(
|
|
2403
2407
|
PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
|
|
2404
2408
|
);
|
|
@@ -2443,14 +2447,14 @@ function copyTextToClipboard(text) {
|
|
|
2443
2447
|
["clip.exe", []]
|
|
2444
2448
|
];
|
|
2445
2449
|
for (const [command, args] of candidates) {
|
|
2446
|
-
if (spawnSync2("which", [command], { stdio: "ignore" }).status !== 0) continue;
|
|
2450
|
+
if (spawnSync2("which", [command], { stdio: "ignore", shell: false }).status !== 0) continue;
|
|
2447
2451
|
if (command === "wl-copy") {
|
|
2448
|
-
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
2452
|
+
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"], shell: false });
|
|
2449
2453
|
child.stdin.end(text);
|
|
2450
2454
|
child.unref();
|
|
2451
2455
|
return true;
|
|
2452
2456
|
}
|
|
2453
|
-
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] }).status === 0) return true;
|
|
2457
|
+
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"], shell: false }).status === 0) return true;
|
|
2454
2458
|
}
|
|
2455
2459
|
return false;
|
|
2456
2460
|
}
|
|
@@ -3017,7 +3021,7 @@ async function bindNativeClaudeSocket(socketPath, onFrame) {
|
|
|
3017
3021
|
}
|
|
3018
3022
|
function processStart(pid) {
|
|
3019
3023
|
return new Promise((resolve5) => {
|
|
3020
|
-
execFile("ps", ["-o", "lstart=", "-p", String(pid)], (error, stdout) => resolve5(error ? void 0 : stdout.trim() || void 0));
|
|
3024
|
+
execFile("ps", ["-o", "lstart=", "-p", String(pid)], { shell: false }, (error, stdout) => resolve5(error ? void 0 : stdout.trim() || void 0));
|
|
3021
3025
|
});
|
|
3022
3026
|
}
|
|
3023
3027
|
async function registerNativeClaudePeer(options) {
|
|
@@ -3201,7 +3205,8 @@ function gitString(cwd, args) {
|
|
|
3201
3205
|
const result = spawnSync3("git", args, {
|
|
3202
3206
|
cwd,
|
|
3203
3207
|
encoding: "utf8",
|
|
3204
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3208
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3209
|
+
shell: false
|
|
3205
3210
|
});
|
|
3206
3211
|
if (result.status !== 0) return null;
|
|
3207
3212
|
const trimmed = result.stdout.trim();
|
|
@@ -3445,6 +3450,7 @@ async function runCciTui(options, id, name, scopeId) {
|
|
|
3445
3450
|
const child = spawn4(options.claudeCommand, args, {
|
|
3446
3451
|
cwd: options.cwd,
|
|
3447
3452
|
stdio: "inherit",
|
|
3453
|
+
shell: false,
|
|
3448
3454
|
env: {
|
|
3449
3455
|
...process.env,
|
|
3450
3456
|
...scopeEnv,
|
package/dist/ccim.mjs
CHANGED
|
@@ -278,7 +278,8 @@ function runClaudeTurn(options) {
|
|
|
278
278
|
child = spawn(command, launch.args, {
|
|
279
279
|
cwd: launch.cwd,
|
|
280
280
|
env: { ...process.env, ...options.env },
|
|
281
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
281
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
282
|
+
shell: false
|
|
282
283
|
});
|
|
283
284
|
} finally {
|
|
284
285
|
launch.release();
|
|
@@ -570,7 +571,8 @@ function isNativeClaudeVersionCompatible(version) {
|
|
|
570
571
|
function probeClaudeVersion(command = "claude") {
|
|
571
572
|
const result = spawnSync(command, ["--version"], {
|
|
572
573
|
encoding: "utf8",
|
|
573
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
574
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
575
|
+
shell: false
|
|
574
576
|
});
|
|
575
577
|
if (result.error || result.status !== 0) {
|
|
576
578
|
return {
|
|
@@ -2067,7 +2069,8 @@ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env)
|
|
|
2067
2069
|
stdio: "ignore",
|
|
2068
2070
|
cwd: extensionDir,
|
|
2069
2071
|
env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
2070
|
-
windowsHide: true
|
|
2072
|
+
windowsHide: true,
|
|
2073
|
+
shell: false
|
|
2071
2074
|
};
|
|
2072
2075
|
}
|
|
2073
2076
|
function toError2(error) {
|
|
@@ -2403,6 +2406,7 @@ function defaultInboxPath(sessionId, intercomDir = getIntercomDirPath()) {
|
|
|
2403
2406
|
|
|
2404
2407
|
// claude/team.ts
|
|
2405
2408
|
import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
|
|
2409
|
+
import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
|
|
2406
2410
|
var BOSS_NONTERMINAL_STATES = new Set(
|
|
2407
2411
|
PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
|
|
2408
2412
|
);
|
|
@@ -2447,14 +2451,14 @@ function copyTextToClipboard(text) {
|
|
|
2447
2451
|
["clip.exe", []]
|
|
2448
2452
|
];
|
|
2449
2453
|
for (const [command, args] of candidates) {
|
|
2450
|
-
if (spawnSync2("which", [command], { stdio: "ignore" }).status !== 0) continue;
|
|
2454
|
+
if (spawnSync2("which", [command], { stdio: "ignore", shell: false }).status !== 0) continue;
|
|
2451
2455
|
if (command === "wl-copy") {
|
|
2452
|
-
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
2456
|
+
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"], shell: false });
|
|
2453
2457
|
child.stdin.end(text);
|
|
2454
2458
|
child.unref();
|
|
2455
2459
|
return true;
|
|
2456
2460
|
}
|
|
2457
|
-
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] }).status === 0) return true;
|
|
2461
|
+
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"], shell: false }).status === 0) return true;
|
|
2458
2462
|
}
|
|
2459
2463
|
return false;
|
|
2460
2464
|
}
|
|
@@ -3021,7 +3025,7 @@ async function bindNativeClaudeSocket(socketPath, onFrame) {
|
|
|
3021
3025
|
}
|
|
3022
3026
|
function processStart(pid) {
|
|
3023
3027
|
return new Promise((resolve5) => {
|
|
3024
|
-
execFile("ps", ["-o", "lstart=", "-p", String(pid)], (error, stdout) => resolve5(error ? void 0 : stdout.trim() || void 0));
|
|
3028
|
+
execFile("ps", ["-o", "lstart=", "-p", String(pid)], { shell: false }, (error, stdout) => resolve5(error ? void 0 : stdout.trim() || void 0));
|
|
3025
3029
|
});
|
|
3026
3030
|
}
|
|
3027
3031
|
async function registerNativeClaudePeer(options) {
|
|
@@ -3205,7 +3209,8 @@ function gitString(cwd, args) {
|
|
|
3205
3209
|
const result = spawnSync3("git", args, {
|
|
3206
3210
|
cwd,
|
|
3207
3211
|
encoding: "utf8",
|
|
3208
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3212
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3213
|
+
shell: false
|
|
3209
3214
|
});
|
|
3210
3215
|
if (result.status !== 0) return null;
|
|
3211
3216
|
const trimmed = result.stdout.trim();
|
|
@@ -3449,6 +3454,7 @@ async function runCciTui(options, id, name, scopeId) {
|
|
|
3449
3454
|
const child = spawn4(options.claudeCommand, args, {
|
|
3450
3455
|
cwd: options.cwd,
|
|
3451
3456
|
stdio: "inherit",
|
|
3457
|
+
shell: false,
|
|
3452
3458
|
env: {
|
|
3453
3459
|
...process.env,
|
|
3454
3460
|
...scopeEnv,
|
package/dist/claude-server.mjs
CHANGED
|
@@ -1501,7 +1501,8 @@ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env)
|
|
|
1501
1501
|
stdio: "ignore",
|
|
1502
1502
|
cwd: extensionDir,
|
|
1503
1503
|
env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
1504
|
-
windowsHide: true
|
|
1504
|
+
windowsHide: true,
|
|
1505
|
+
shell: false
|
|
1505
1506
|
};
|
|
1506
1507
|
}
|
|
1507
1508
|
function toError2(error2) {
|
|
@@ -1870,6 +1871,7 @@ function appendInboxMessage(path, from, message) {
|
|
|
1870
1871
|
import { readFile } from "node:fs/promises";
|
|
1871
1872
|
import { join as join6 } from "node:path";
|
|
1872
1873
|
import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
|
|
1874
|
+
import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
|
|
1873
1875
|
var LEGACY_LIVE_STATES = /* @__PURE__ */ new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
|
|
1874
1876
|
var BOSS_NONTERMINAL_STATES = new Set(
|
|
1875
1877
|
PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
|
|
@@ -1948,33 +1950,139 @@ function teamMember(worker, session) {
|
|
|
1948
1950
|
connected: session.id === id
|
|
1949
1951
|
};
|
|
1950
1952
|
}
|
|
1951
|
-
function
|
|
1953
|
+
async function resolveNonAuthoritativeTeam(input, env) {
|
|
1954
|
+
if (env.AGENT_INTERCOM_TEAM_MANIFEST !== void 0) {
|
|
1955
|
+
const rawPath = env.AGENT_INTERCOM_TEAM_MANIFEST.trim();
|
|
1956
|
+
if (!rawPath) {
|
|
1957
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
1958
|
+
}
|
|
1959
|
+
const manifest = await readTeamManifestAsync(rawPath);
|
|
1960
|
+
if (!manifest) {
|
|
1961
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
1962
|
+
}
|
|
1963
|
+
const selfMember = manifest.members.find((m) => m.sessionId === input.selfId);
|
|
1964
|
+
if (!selfMember) {
|
|
1965
|
+
throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
|
|
1966
|
+
}
|
|
1967
|
+
const isManager = input.selfId === manifest.leadId;
|
|
1968
|
+
const managerTarget2 = manifest.leadId;
|
|
1969
|
+
const managerConnected = connectedTo(input.sessions, managerTarget2);
|
|
1970
|
+
const coworkers = manifest.members.filter((m) => m.sessionId !== input.selfId).filter((m) => isManager || m.sessionId !== managerTarget2).map((m) => {
|
|
1971
|
+
const live = input.sessions.find((s) => s.id === m.sessionId || s.name === m.sessionId);
|
|
1972
|
+
return {
|
|
1973
|
+
id: m.sessionId,
|
|
1974
|
+
target: m.sessionId,
|
|
1975
|
+
role: m.role,
|
|
1976
|
+
connected: Boolean(live)
|
|
1977
|
+
};
|
|
1978
|
+
});
|
|
1979
|
+
return {
|
|
1980
|
+
teamId: manifest.runId,
|
|
1981
|
+
self: { id: input.selfId, isManager },
|
|
1982
|
+
manager: { target: managerTarget2, connected: managerConnected },
|
|
1983
|
+
coworkers,
|
|
1984
|
+
source: "manifest"
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
if (stringValue(env.AGENT_INTERCOM_SCOPE_ID)) {
|
|
1988
|
+
const managerTarget2 = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
1989
|
+
const role = stringValue(env.AGENT_INTERCOM_ROLE)?.toLowerCase();
|
|
1990
|
+
const isManager = role === "manager" || managerTarget2 !== void 0 && managerTarget2 === input.selfId || managerTarget2 === void 0 && role !== "worker";
|
|
1991
|
+
const effectiveManagerTarget = isManager ? input.selfId : managerTarget2;
|
|
1992
|
+
const coworkers = input.sessions.filter((session) => session.id !== input.selfId).filter((session) => session.model !== "human").filter((session) => session.id !== effectiveManagerTarget).map((session) => ({
|
|
1993
|
+
id: session.id,
|
|
1994
|
+
target: session.id,
|
|
1995
|
+
...session.model ? { harness: session.model } : {},
|
|
1996
|
+
connected: true
|
|
1997
|
+
}));
|
|
1998
|
+
const manager = effectiveManagerTarget ? {
|
|
1999
|
+
target: effectiveManagerTarget,
|
|
2000
|
+
connected: isManager ? true : connectedTo(input.sessions, effectiveManagerTarget)
|
|
2001
|
+
} : void 0;
|
|
2002
|
+
return {
|
|
2003
|
+
teamId: effectiveManagerTarget ?? input.selfId,
|
|
2004
|
+
self: { id: input.selfId, isManager },
|
|
2005
|
+
...manager ? { manager } : {},
|
|
2006
|
+
coworkers,
|
|
2007
|
+
source: "live"
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
2011
|
+
return {
|
|
2012
|
+
teamId: managerTarget ?? input.selfId,
|
|
2013
|
+
self: { id: input.selfId, isManager: !managerTarget },
|
|
2014
|
+
manager: managerTarget ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) } : { target: input.selfId, connected: true },
|
|
2015
|
+
coworkers: [],
|
|
2016
|
+
source: "standalone"
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
async function resolveLegacyTeam(input, workers, env) {
|
|
1952
2020
|
const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
|
|
1953
2021
|
const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
|
|
1954
2022
|
const current = workerId ? workers.find(
|
|
1955
2023
|
(worker) => stringValue(worker.id) === workerId && (!runId || stringValue(worker.runId) === runId)
|
|
1956
2024
|
) : void 0;
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2025
|
+
if (current) {
|
|
2026
|
+
const managerTarget = stringValue(current?.managerSessionId) ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
|
|
2027
|
+
if (!managerTarget) {
|
|
2028
|
+
return {
|
|
2029
|
+
self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
|
|
2030
|
+
coworkers: [],
|
|
2031
|
+
source: "orchestrator"
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
const coworkers = workers.filter((worker) => worker.owned === true).filter((worker) => {
|
|
2035
|
+
const mgr = stringValue(worker.managerSessionId);
|
|
2036
|
+
return mgr !== void 0 && mgr === managerTarget;
|
|
2037
|
+
}).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== workerId && stringValue(worker.id) !== input.selfId).map((worker) => {
|
|
2038
|
+
const id = stringValue(worker.id);
|
|
2039
|
+
if (!id) return void 0;
|
|
2040
|
+
const target = stringValue(worker.intercomTarget) ?? id;
|
|
2041
|
+
return {
|
|
2042
|
+
id,
|
|
2043
|
+
target,
|
|
2044
|
+
...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
|
|
2045
|
+
...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
|
|
2046
|
+
...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
|
|
2047
|
+
connected: connectedTo(input.sessions, target)
|
|
2048
|
+
};
|
|
2049
|
+
}).filter((member) => Boolean(member));
|
|
1963
2050
|
return {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
connected: connectedTo(input.sessions, target)
|
|
2051
|
+
teamId: managerTarget,
|
|
2052
|
+
self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
|
|
2053
|
+
manager: { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) },
|
|
2054
|
+
coworkers,
|
|
2055
|
+
source: "orchestrator"
|
|
1970
2056
|
};
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2057
|
+
}
|
|
2058
|
+
if (workerId === void 0) {
|
|
2059
|
+
const ownedCoworkers = workers.filter((worker) => worker.owned === true).filter((worker) => {
|
|
2060
|
+
const mgr = stringValue(worker.managerSessionId);
|
|
2061
|
+
return mgr !== void 0 && mgr === input.selfId;
|
|
2062
|
+
}).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== input.selfId).map((worker) => {
|
|
2063
|
+
const id = stringValue(worker.id);
|
|
2064
|
+
if (!id) return void 0;
|
|
2065
|
+
const target = stringValue(worker.intercomTarget) ?? id;
|
|
2066
|
+
return {
|
|
2067
|
+
id,
|
|
2068
|
+
target,
|
|
2069
|
+
...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
|
|
2070
|
+
...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
|
|
2071
|
+
...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
|
|
2072
|
+
connected: connectedTo(input.sessions, target)
|
|
2073
|
+
};
|
|
2074
|
+
}).filter((member) => Boolean(member));
|
|
2075
|
+
if (ownedCoworkers.length > 0) {
|
|
2076
|
+
return {
|
|
2077
|
+
teamId: input.selfId,
|
|
2078
|
+
self: { id: input.selfId, isManager: true },
|
|
2079
|
+
manager: { target: input.selfId, connected: true },
|
|
2080
|
+
coworkers: ownedCoworkers,
|
|
2081
|
+
source: "orchestrator"
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return resolveNonAuthoritativeTeam(input, env);
|
|
1978
2086
|
}
|
|
1979
2087
|
async function resolveIntercomTeam(input) {
|
|
1980
2088
|
const env = input.env ?? process.env;
|
|
@@ -1987,7 +2095,8 @@ async function resolveIntercomTeam(input) {
|
|
|
1987
2095
|
return {
|
|
1988
2096
|
teamId: selfSession.boss.binding.bossRunId,
|
|
1989
2097
|
self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
|
|
1990
|
-
coworkers: []
|
|
2098
|
+
coworkers: [],
|
|
2099
|
+
source: "boss"
|
|
1991
2100
|
};
|
|
1992
2101
|
}
|
|
1993
2102
|
const currentRole = selfSession.boss.binding.role;
|
|
@@ -2000,7 +2109,8 @@ async function resolveIntercomTeam(input) {
|
|
|
2000
2109
|
return {
|
|
2001
2110
|
teamId: selfSession.boss.binding.bossRunId,
|
|
2002
2111
|
self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
|
|
2003
|
-
coworkers: []
|
|
2112
|
+
coworkers: [],
|
|
2113
|
+
source: "boss"
|
|
2004
2114
|
};
|
|
2005
2115
|
}
|
|
2006
2116
|
const coworkers = workers.filter((worker) => exactStringValue(worker.id) !== workerId).filter((worker) => exactStringValue(worker.managerSessionId) === managerTarget).map((worker) => {
|
|
@@ -2014,7 +2124,8 @@ async function resolveIntercomTeam(input) {
|
|
|
2014
2124
|
teamId: selfSession.boss.binding.bossRunId,
|
|
2015
2125
|
self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: selfIsManager },
|
|
2016
2126
|
manager: { target: managerTarget, connected: true },
|
|
2017
|
-
coworkers
|
|
2127
|
+
coworkers,
|
|
2128
|
+
source: "boss"
|
|
2018
2129
|
};
|
|
2019
2130
|
}
|
|
2020
2131
|
function formatIntercomTeam(team) {
|
|
@@ -2082,10 +2193,23 @@ function publicPendingEntry(entry, selector) {
|
|
|
2082
2193
|
function shortHash(value) {
|
|
2083
2194
|
return createHash2("sha256").update(value).digest("hex").slice(0, 8);
|
|
2084
2195
|
}
|
|
2196
|
+
var GENERIC_SESSION_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
|
|
2085
2197
|
function buildClaudeRuntimeIdentity(env = process.env, cwd = env.PWD || processCwd(), pid = process.pid) {
|
|
2086
|
-
|
|
2198
|
+
let sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim() || env.CLAUDE_PEER_ID?.trim();
|
|
2199
|
+
if (!sessionId && env.AGENT_INTERCOM_SESSION_ID !== void 0) {
|
|
2200
|
+
const raw = env.AGENT_INTERCOM_SESSION_ID.trim();
|
|
2201
|
+
if (raw) {
|
|
2202
|
+
if (!GENERIC_SESSION_ID_REGEX.test(raw)) {
|
|
2203
|
+
throw new Error("Invalid AGENT_INTERCOM_SESSION_ID: must match ^[A-Za-z0-9_-]{1,128}$");
|
|
2204
|
+
}
|
|
2205
|
+
sessionId = raw;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (!sessionId) {
|
|
2209
|
+
sessionId = `claude-${pid}-${shortHash(cwd)}`;
|
|
2210
|
+
}
|
|
2087
2211
|
const cwdName = basename(cwd) || "workspace";
|
|
2088
|
-
const name = env.CLAUDE_INTERCOM_NAME?.trim() || env.CLAUDE_PEER_NAME?.trim() || `claude-${cwdName}-${pid}`;
|
|
2212
|
+
const name = env.CLAUDE_INTERCOM_NAME?.trim() || env.CLAUDE_PEER_NAME?.trim() || (env.AGENT_INTERCOM_SESSION_NAME !== void 0 && env.AGENT_INTERCOM_SESSION_NAME.trim() ? env.AGENT_INTERCOM_SESSION_NAME.trim() : void 0) || `claude-${cwdName}-${pid}`;
|
|
2089
2213
|
return {
|
|
2090
2214
|
sessionId,
|
|
2091
2215
|
name,
|
|
@@ -2151,7 +2275,8 @@ function detectGitRoot(cwd) {
|
|
|
2151
2275
|
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
2152
2276
|
cwd,
|
|
2153
2277
|
encoding: "utf8",
|
|
2154
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2278
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2279
|
+
shell: false
|
|
2155
2280
|
});
|
|
2156
2281
|
if (result.status !== 0) return null;
|
|
2157
2282
|
return result.stdout.trim() || null;
|
|
@@ -2506,18 +2631,21 @@ function buildToolDefinitions(runtime2) {
|
|
|
2506
2631
|
name: "intercom_whoami",
|
|
2507
2632
|
description: "Return this Claude session's intercom identity for reliable targeting.",
|
|
2508
2633
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2634
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2509
2635
|
handler: async () => runtime2.whoami()
|
|
2510
2636
|
},
|
|
2511
2637
|
{
|
|
2512
2638
|
name: "intercom_team",
|
|
2513
2639
|
description: "Show your current manager and the live coworkers owned by that manager. No arguments are required.",
|
|
2514
2640
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2641
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2515
2642
|
handler: async () => runtime2.team()
|
|
2516
2643
|
},
|
|
2517
2644
|
{
|
|
2518
2645
|
name: "intercom_status",
|
|
2519
2646
|
description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
|
|
2520
2647
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
2648
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2521
2649
|
handler: async () => runtime2.status()
|
|
2522
2650
|
},
|
|
2523
2651
|
{
|
|
@@ -2531,6 +2659,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2531
2659
|
},
|
|
2532
2660
|
additionalProperties: false
|
|
2533
2661
|
},
|
|
2662
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
2534
2663
|
handler: async (args) => runtime2.list(
|
|
2535
2664
|
args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
|
|
2536
2665
|
asBoolean(args.include_self, false)
|
|
@@ -2545,6 +2674,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2545
2674
|
required: ["summary"],
|
|
2546
2675
|
additionalProperties: false
|
|
2547
2676
|
},
|
|
2677
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
2548
2678
|
handler: async (args) => runtime2.setSummary(asString(args.summary, "summary"))
|
|
2549
2679
|
},
|
|
2550
2680
|
{
|
|
@@ -2560,6 +2690,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2560
2690
|
required: ["to", "message"],
|
|
2561
2691
|
additionalProperties: false
|
|
2562
2692
|
},
|
|
2693
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2563
2694
|
handler: async (args) => runtime2.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments))
|
|
2564
2695
|
},
|
|
2565
2696
|
{
|
|
@@ -2576,6 +2707,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2576
2707
|
required: ["to", "message"],
|
|
2577
2708
|
additionalProperties: false
|
|
2578
2709
|
},
|
|
2710
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2579
2711
|
handler: async (args, signal) => runtime2.ask(
|
|
2580
2712
|
asString(args.to, "to"),
|
|
2581
2713
|
asString(args.message, "message"),
|
|
@@ -2592,6 +2724,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2592
2724
|
properties: { mark_read: { type: "boolean", default: false } },
|
|
2593
2725
|
additionalProperties: false
|
|
2594
2726
|
},
|
|
2727
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
2595
2728
|
handler: async (args) => runtime2.pending(asBoolean(args.mark_read, false))
|
|
2596
2729
|
},
|
|
2597
2730
|
{
|
|
@@ -2607,6 +2740,7 @@ function buildToolDefinitions(runtime2) {
|
|
|
2607
2740
|
required: ["message"],
|
|
2608
2741
|
additionalProperties: false
|
|
2609
2742
|
},
|
|
2743
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2610
2744
|
handler: async (args) => runtime2.reply(asString(args.message, "message"), typeof args.to === "string" ? args.to : void 0, args.which === "oldest" || args.which === "latest" ? args.which : void 0)
|
|
2611
2745
|
}
|
|
2612
2746
|
];
|
|
@@ -2644,7 +2778,7 @@ async function handleMcpRequest(request, runtime2) {
|
|
|
2644
2778
|
return ok(request.id, {});
|
|
2645
2779
|
case "tools/list":
|
|
2646
2780
|
return ok(request.id, {
|
|
2647
|
-
tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }))
|
|
2781
|
+
tools: tools.map(({ name, description, inputSchema, annotations }) => ({ name, description, inputSchema, annotations }))
|
|
2648
2782
|
});
|
|
2649
2783
|
case "tools/call": {
|
|
2650
2784
|
const name = request.params?.name;
|
package/dist/worker-daemon.mjs
CHANGED
|
@@ -265,7 +265,8 @@ function runClaudeTurn(options) {
|
|
|
265
265
|
child = spawn(command, launch.args, {
|
|
266
266
|
cwd: launch.cwd,
|
|
267
267
|
env: { ...process.env, ...options.env },
|
|
268
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
268
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
269
|
+
shell: false
|
|
269
270
|
});
|
|
270
271
|
} finally {
|
|
271
272
|
launch.release();
|
|
@@ -557,7 +558,8 @@ function isNativeClaudeVersionCompatible(version) {
|
|
|
557
558
|
function probeClaudeVersion(command = "claude") {
|
|
558
559
|
const result = spawnSync(command, ["--version"], {
|
|
559
560
|
encoding: "utf8",
|
|
560
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
561
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
562
|
+
shell: false
|
|
561
563
|
});
|
|
562
564
|
if (result.error || result.status !== 0) {
|
|
563
565
|
return {
|
|
@@ -2054,7 +2056,8 @@ function getBrokerSpawnOptions(extensionDir = EXTENSION_DIR, env = process.env)
|
|
|
2054
2056
|
stdio: "ignore",
|
|
2055
2057
|
cwd: extensionDir,
|
|
2056
2058
|
env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
|
|
2057
|
-
windowsHide: true
|
|
2059
|
+
windowsHide: true,
|
|
2060
|
+
shell: false
|
|
2058
2061
|
};
|
|
2059
2062
|
}
|
|
2060
2063
|
function toError2(error) {
|
|
@@ -2381,6 +2384,7 @@ function loadConfig() {
|
|
|
2381
2384
|
|
|
2382
2385
|
// claude/team.ts
|
|
2383
2386
|
import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
|
|
2387
|
+
import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
|
|
2384
2388
|
var BOSS_NONTERMINAL_STATES = new Set(
|
|
2385
2389
|
PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
|
|
2386
2390
|
);
|
|
@@ -2425,14 +2429,14 @@ function copyTextToClipboard(text) {
|
|
|
2425
2429
|
["clip.exe", []]
|
|
2426
2430
|
];
|
|
2427
2431
|
for (const [command, args] of candidates) {
|
|
2428
|
-
if (spawnSync2("which", [command], { stdio: "ignore" }).status !== 0) continue;
|
|
2432
|
+
if (spawnSync2("which", [command], { stdio: "ignore", shell: false }).status !== 0) continue;
|
|
2429
2433
|
if (command === "wl-copy") {
|
|
2430
|
-
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
2434
|
+
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"], shell: false });
|
|
2431
2435
|
child.stdin.end(text);
|
|
2432
2436
|
child.unref();
|
|
2433
2437
|
return true;
|
|
2434
2438
|
}
|
|
2435
|
-
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] }).status === 0) return true;
|
|
2439
|
+
if (spawnSync2(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"], shell: false }).status === 0) return true;
|
|
2436
2440
|
}
|
|
2437
2441
|
return false;
|
|
2438
2442
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctliz/agent-intercom-claude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0-connect.3",
|
|
4
4
|
"description": "Claude Code MCP plugin and wakeable worker for direct local messaging with Pi, Codex, and Claude coding-agent sessions.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
"codex"
|
|
68
68
|
],
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@ctliz/agent-intercom-core": "0.
|
|
70
|
+
"@ctliz/agent-intercom-core": "0.2.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@ctliz/agent-intercom-core": "
|
|
73
|
+
"@ctliz/agent-intercom-core": "0.2.0",
|
|
74
74
|
"@types/node": "^24.0.0",
|
|
75
75
|
"esbuild": "^0.28.1",
|
|
76
76
|
"tsx": "^4.20.0",
|
|
@@ -5,25 +5,25 @@ import { spawnSync } from "node:child_process";
|
|
|
5
5
|
|
|
6
6
|
export const APPROVED_CORE = Object.freeze({
|
|
7
7
|
name: "@ctliz/agent-intercom-core",
|
|
8
|
-
version: "0.
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
version: "0.2.0",
|
|
9
|
+
integrity: "sha512-bpifL9cc8cwMm74fpvjgRBarXMwn6BY4cST4ry6HrGtfpRTXyiJOtwfNnhORF6xTKwQNOWakxS1sZALczInvkQ==",
|
|
10
|
+
resolved: "https://registry.npmjs.org/@ctliz/agent-intercom-core/-/agent-intercom-core-0.2.0.tgz",
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const repositoryRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
-
const devSpec =
|
|
14
|
+
const devSpec = APPROVED_CORE.version;
|
|
15
15
|
|
|
16
16
|
function readJson(path) {
|
|
17
17
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function requireEqual(actual, expected, field) {
|
|
20
|
+
export function requireEqual(actual, expected, field) {
|
|
21
21
|
if (actual !== expected) {
|
|
22
22
|
throw new Error(`${field} must be ${JSON.stringify(expected)}; received ${JSON.stringify(actual)}`);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function verifyLocalProvenance() {
|
|
26
|
+
export function verifyLocalProvenance() {
|
|
27
27
|
const manifest = readJson(join(repositoryRoot, "package.json"));
|
|
28
28
|
const lock = readJson(join(repositoryRoot, "package-lock.json"));
|
|
29
29
|
const lockRoot = lock.packages?.[""];
|
|
@@ -36,28 +36,30 @@ function verifyLocalProvenance() {
|
|
|
36
36
|
requireEqual(lockRoot?.peerDependencies?.[APPROVED_CORE.name], APPROVED_CORE.version, "locked Core peer dependency");
|
|
37
37
|
requireEqual(lockRoot?.devDependencies?.[APPROVED_CORE.name], devSpec, "locked Core development provenance");
|
|
38
38
|
requireEqual(lockedCore?.version, APPROVED_CORE.version, "supplied Core version");
|
|
39
|
-
requireEqual(lockedCore?.resolved,
|
|
39
|
+
requireEqual(lockedCore?.resolved, APPROVED_CORE.resolved, "supplied Core resolved URL");
|
|
40
40
|
requireEqual(lockedCore?.integrity, APPROVED_CORE.integrity, "supplied Core artifact integrity");
|
|
41
41
|
requireEqual(lockedCore?.dev, true, "supplied Core dependency class");
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
44
|
+
export function parseAndVerifyPublishedProvenance(jsonString) {
|
|
45
|
+
const published = typeof jsonString === "string" ? JSON.parse(jsonString) : jsonString;
|
|
46
|
+
requireEqual(published.version, APPROVED_CORE.version, "published Core version");
|
|
47
|
+
requireEqual(published["dist.integrity"] ?? published.dist?.integrity, APPROVED_CORE.integrity, "published Core artifact integrity");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function verifyPublishedProvenance(runner = spawnSync) {
|
|
51
|
+
const result = runner("npm", [
|
|
46
52
|
"view",
|
|
47
53
|
`${APPROVED_CORE.name}@${APPROVED_CORE.version}`,
|
|
48
54
|
"version",
|
|
49
55
|
"dist.integrity",
|
|
50
|
-
"gitHead",
|
|
51
56
|
"--json",
|
|
52
|
-
], { encoding: "utf8" });
|
|
57
|
+
], { encoding: "utf8", shell: false });
|
|
53
58
|
if (result.error) throw result.error;
|
|
54
59
|
if (result.status !== 0) {
|
|
55
60
|
throw new Error(`Unable to inspect published Core ${APPROVED_CORE.version}: ${(result.stderr || result.stdout).trim()}`);
|
|
56
61
|
}
|
|
57
|
-
|
|
58
|
-
requireEqual(published.version, APPROVED_CORE.version, "published Core version");
|
|
59
|
-
requireEqual(published["dist.integrity"] ?? published.dist?.integrity, APPROVED_CORE.integrity, "published Core artifact integrity");
|
|
60
|
-
requireEqual(published.gitHead, APPROVED_CORE.commit, "published Core commit");
|
|
62
|
+
parseAndVerifyPublishedProvenance(result.stdout);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
verifyLocalProvenance();
|
|
@@ -6,7 +6,7 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
|
|
7
7
|
const repositoryRoot = resolve(import.meta.dirname, "..");
|
|
8
8
|
const corePackage = "@ctliz/agent-intercom-core";
|
|
9
|
-
const coreDevSpec = "
|
|
9
|
+
const coreDevSpec = "0.2.0";
|
|
10
10
|
const coreConsumers = new Set([
|
|
11
11
|
"broker.mjs",
|
|
12
12
|
"cci.mjs",
|
|
@@ -51,6 +51,7 @@ function createPackageArchive(temp) {
|
|
|
51
51
|
cwd: repositoryRoot,
|
|
52
52
|
env: { ...process.env, npm_config_cache: join(temp, "npm-cache") },
|
|
53
53
|
encoding: "utf8",
|
|
54
|
+
shell: false,
|
|
54
55
|
});
|
|
55
56
|
if (result.error) throw result.error;
|
|
56
57
|
if (result.status !== 0) throw new Error(`npm pack failed: ${result.stderr || result.stdout}`);
|
|
@@ -72,10 +73,10 @@ try {
|
|
|
72
73
|
const manifestEntry = entries.get("package/package.json");
|
|
73
74
|
if (!manifestEntry) throw new Error("Packed adapter is missing package.json");
|
|
74
75
|
const manifest = JSON.parse(manifestEntry.toString("utf8"));
|
|
75
|
-
if (manifest.peerDependencies?.[corePackage] !== "0.
|
|
76
|
+
if (manifest.peerDependencies?.[corePackage] !== "0.2.0") throw new Error("Packed adapter must require Core peer 0.2.0");
|
|
76
77
|
if (manifest.peerDependenciesMeta?.[corePackage]?.optional === true) throw new Error("Packed Core peer must not be optional");
|
|
77
78
|
if (manifest.dependencies?.[corePackage] !== undefined) throw new Error("Packed adapter must not install a private Core dependency");
|
|
78
|
-
if (manifest.devDependencies?.[corePackage] !== coreDevSpec) throw new Error("Packed adapter lost the approved Core
|
|
79
|
+
if (manifest.devDependencies?.[corePackage] !== coreDevSpec) throw new Error("Packed adapter lost the approved Core dependency");
|
|
79
80
|
if ([...entries.keys()].some((path) => path.includes("node_modules/@ctliz/agent-intercom-core/"))) {
|
|
80
81
|
throw new Error("Packed adapter contains a private Core module tree");
|
|
81
82
|
}
|
|
@@ -134,7 +135,7 @@ try {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
console.log(`Verified complete plugin+MCP+monitors+dist chain and ${bundles.length} packed bundles using Core peer 0.
|
|
138
|
+
console.log(`Verified complete plugin+MCP+monitors+dist chain and ${bundles.length} packed bundles using Core peer 0.2.0`);
|
|
138
139
|
} finally {
|
|
139
140
|
if (temp) rmSync(temp, { recursive: true, force: true });
|
|
140
141
|
}
|