@ctliz/agent-intercom-claude 0.12.0-connect.3 → 0.13.0-connect.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.
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
- const sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim()
101
- || env.CLAUDE_PEER_ID?.trim()
102
- || `claude-${pid}-${shortHash(cwd)}`;
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,
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 resolveLegacyTeam(
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
- const managerTarget = stringValue(current?.managerSessionId)
229
- ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
230
- ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
231
- const teamId = managerTarget ?? input.selfId;
232
- const coworkers = workers
233
- .filter((worker) => worker.owned === true)
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
- target,
244
- ...(stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {}),
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
- .filter((member): member is TeamMember => Boolean(member));
251
- return {
252
- teamId,
253
- self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: !managerTarget },
254
- manager: managerTarget
255
- ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) }
256
- : { target: input.selfId, connected: true },
257
- coworkers,
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/dist/cci.mjs CHANGED
@@ -2399,6 +2399,7 @@ function defaultInboxPath(sessionId, intercomDir = getIntercomDirPath()) {
2399
2399
 
2400
2400
  // claude/team.ts
2401
2401
  import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
2402
+ import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
2402
2403
  var BOSS_NONTERMINAL_STATES = new Set(
2403
2404
  PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
2404
2405
  );
package/dist/ccim.mjs CHANGED
@@ -2403,6 +2403,7 @@ function defaultInboxPath(sessionId, intercomDir = getIntercomDirPath()) {
2403
2403
 
2404
2404
  // claude/team.ts
2405
2405
  import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
2406
+ import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
2406
2407
  var BOSS_NONTERMINAL_STATES = new Set(
2407
2408
  PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
2408
2409
  );
@@ -1870,6 +1870,7 @@ function appendInboxMessage(path, from, message) {
1870
1870
  import { readFile } from "node:fs/promises";
1871
1871
  import { join as join6 } from "node:path";
1872
1872
  import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
1873
+ import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
1873
1874
  var LEGACY_LIVE_STATES = /* @__PURE__ */ new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
1874
1875
  var BOSS_NONTERMINAL_STATES = new Set(
1875
1876
  PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
@@ -1948,33 +1949,139 @@ function teamMember(worker, session) {
1948
1949
  connected: session.id === id
1949
1950
  };
1950
1951
  }
1951
- function resolveLegacyTeam(input, workers, env) {
1952
+ async function resolveNonAuthoritativeTeam(input, env) {
1953
+ if (env.AGENT_INTERCOM_TEAM_MANIFEST !== void 0) {
1954
+ const rawPath = env.AGENT_INTERCOM_TEAM_MANIFEST.trim();
1955
+ if (!rawPath) {
1956
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
1957
+ }
1958
+ const manifest = await readTeamManifestAsync(rawPath);
1959
+ if (!manifest) {
1960
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
1961
+ }
1962
+ const selfMember = manifest.members.find((m) => m.sessionId === input.selfId);
1963
+ if (!selfMember) {
1964
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
1965
+ }
1966
+ const isManager = input.selfId === manifest.leadId;
1967
+ const managerTarget2 = manifest.leadId;
1968
+ const managerConnected = connectedTo(input.sessions, managerTarget2);
1969
+ const coworkers = manifest.members.filter((m) => m.sessionId !== input.selfId).filter((m) => isManager || m.sessionId !== managerTarget2).map((m) => {
1970
+ const live = input.sessions.find((s) => s.id === m.sessionId || s.name === m.sessionId);
1971
+ return {
1972
+ id: m.sessionId,
1973
+ target: m.sessionId,
1974
+ role: m.role,
1975
+ connected: Boolean(live)
1976
+ };
1977
+ });
1978
+ return {
1979
+ teamId: manifest.runId,
1980
+ self: { id: input.selfId, isManager },
1981
+ manager: { target: managerTarget2, connected: managerConnected },
1982
+ coworkers,
1983
+ source: "manifest"
1984
+ };
1985
+ }
1986
+ if (stringValue(env.AGENT_INTERCOM_SCOPE_ID)) {
1987
+ const managerTarget2 = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
1988
+ const role = stringValue(env.AGENT_INTERCOM_ROLE)?.toLowerCase();
1989
+ const isManager = role === "manager" || managerTarget2 !== void 0 && managerTarget2 === input.selfId || managerTarget2 === void 0 && role !== "worker";
1990
+ const effectiveManagerTarget = isManager ? input.selfId : managerTarget2;
1991
+ const coworkers = input.sessions.filter((session) => session.id !== input.selfId).filter((session) => session.model !== "human").filter((session) => session.id !== effectiveManagerTarget).map((session) => ({
1992
+ id: session.id,
1993
+ target: session.id,
1994
+ ...session.model ? { harness: session.model } : {},
1995
+ connected: true
1996
+ }));
1997
+ const manager = effectiveManagerTarget ? {
1998
+ target: effectiveManagerTarget,
1999
+ connected: isManager ? true : connectedTo(input.sessions, effectiveManagerTarget)
2000
+ } : void 0;
2001
+ return {
2002
+ teamId: effectiveManagerTarget ?? input.selfId,
2003
+ self: { id: input.selfId, isManager },
2004
+ ...manager ? { manager } : {},
2005
+ coworkers,
2006
+ source: "live"
2007
+ };
2008
+ }
2009
+ const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
2010
+ return {
2011
+ teamId: managerTarget ?? input.selfId,
2012
+ self: { id: input.selfId, isManager: !managerTarget },
2013
+ manager: managerTarget ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) } : { target: input.selfId, connected: true },
2014
+ coworkers: [],
2015
+ source: "standalone"
2016
+ };
2017
+ }
2018
+ async function resolveLegacyTeam(input, workers, env) {
1952
2019
  const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
1953
2020
  const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
1954
2021
  const current = workerId ? workers.find(
1955
2022
  (worker) => stringValue(worker.id) === workerId && (!runId || stringValue(worker.runId) === runId)
1956
2023
  ) : void 0;
1957
- const managerTarget = stringValue(current?.managerSessionId) ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
1958
- const teamId = managerTarget ?? input.selfId;
1959
- const coworkers = workers.filter((worker) => worker.owned === true).filter((worker) => stringValue(worker.managerSessionId) === teamId).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== workerId).map((worker) => {
1960
- const id = stringValue(worker.id);
1961
- if (!id) return void 0;
1962
- const target = stringValue(worker.intercomTarget) ?? id;
2024
+ if (current) {
2025
+ const managerTarget = stringValue(current?.managerSessionId) ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
2026
+ if (!managerTarget) {
2027
+ return {
2028
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
2029
+ coworkers: [],
2030
+ source: "orchestrator"
2031
+ };
2032
+ }
2033
+ const coworkers = workers.filter((worker) => worker.owned === true).filter((worker) => {
2034
+ const mgr = stringValue(worker.managerSessionId);
2035
+ return mgr !== void 0 && mgr === managerTarget;
2036
+ }).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== workerId && stringValue(worker.id) !== input.selfId).map((worker) => {
2037
+ const id = stringValue(worker.id);
2038
+ if (!id) return void 0;
2039
+ const target = stringValue(worker.intercomTarget) ?? id;
2040
+ return {
2041
+ id,
2042
+ target,
2043
+ ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
2044
+ ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
2045
+ ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
2046
+ connected: connectedTo(input.sessions, target)
2047
+ };
2048
+ }).filter((member) => Boolean(member));
1963
2049
  return {
1964
- id,
1965
- target,
1966
- ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
1967
- ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
1968
- ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
1969
- connected: connectedTo(input.sessions, target)
2050
+ teamId: managerTarget,
2051
+ self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
2052
+ manager: { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) },
2053
+ coworkers,
2054
+ source: "orchestrator"
1970
2055
  };
1971
- }).filter((member) => Boolean(member));
1972
- return {
1973
- teamId,
1974
- self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: !managerTarget },
1975
- manager: managerTarget ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) } : { target: input.selfId, connected: true },
1976
- coworkers
1977
- };
2056
+ }
2057
+ if (workerId === void 0) {
2058
+ const ownedCoworkers = workers.filter((worker) => worker.owned === true).filter((worker) => {
2059
+ const mgr = stringValue(worker.managerSessionId);
2060
+ return mgr !== void 0 && mgr === input.selfId;
2061
+ }).filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? "")).filter((worker) => stringValue(worker.id) !== input.selfId).map((worker) => {
2062
+ const id = stringValue(worker.id);
2063
+ if (!id) return void 0;
2064
+ const target = stringValue(worker.intercomTarget) ?? id;
2065
+ return {
2066
+ id,
2067
+ target,
2068
+ ...stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {},
2069
+ ...stringValue(worker.role) ? { role: stringValue(worker.role) } : {},
2070
+ ...stringValue(worker.state) ? { state: stringValue(worker.state) } : {},
2071
+ connected: connectedTo(input.sessions, target)
2072
+ };
2073
+ }).filter((member) => Boolean(member));
2074
+ if (ownedCoworkers.length > 0) {
2075
+ return {
2076
+ teamId: input.selfId,
2077
+ self: { id: input.selfId, isManager: true },
2078
+ manager: { target: input.selfId, connected: true },
2079
+ coworkers: ownedCoworkers,
2080
+ source: "orchestrator"
2081
+ };
2082
+ }
2083
+ }
2084
+ return resolveNonAuthoritativeTeam(input, env);
1978
2085
  }
1979
2086
  async function resolveIntercomTeam(input) {
1980
2087
  const env = input.env ?? process.env;
@@ -1987,7 +2094,8 @@ async function resolveIntercomTeam(input) {
1987
2094
  return {
1988
2095
  teamId: selfSession.boss.binding.bossRunId,
1989
2096
  self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
1990
- coworkers: []
2097
+ coworkers: [],
2098
+ source: "boss"
1991
2099
  };
1992
2100
  }
1993
2101
  const currentRole = selfSession.boss.binding.role;
@@ -2000,7 +2108,8 @@ async function resolveIntercomTeam(input) {
2000
2108
  return {
2001
2109
  teamId: selfSession.boss.binding.bossRunId,
2002
2110
  self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: false },
2003
- coworkers: []
2111
+ coworkers: [],
2112
+ source: "boss"
2004
2113
  };
2005
2114
  }
2006
2115
  const coworkers = workers.filter((worker) => exactStringValue(worker.id) !== workerId).filter((worker) => exactStringValue(worker.managerSessionId) === managerTarget).map((worker) => {
@@ -2014,7 +2123,8 @@ async function resolveIntercomTeam(input) {
2014
2123
  teamId: selfSession.boss.binding.bossRunId,
2015
2124
  self: { id: input.selfId, ...workerId ? { workerId } : {}, isManager: selfIsManager },
2016
2125
  manager: { target: managerTarget, connected: true },
2017
- coworkers
2126
+ coworkers,
2127
+ source: "boss"
2018
2128
  };
2019
2129
  }
2020
2130
  function formatIntercomTeam(team) {
@@ -2082,10 +2192,23 @@ function publicPendingEntry(entry, selector) {
2082
2192
  function shortHash(value) {
2083
2193
  return createHash2("sha256").update(value).digest("hex").slice(0, 8);
2084
2194
  }
2195
+ var GENERIC_SESSION_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
2085
2196
  function buildClaudeRuntimeIdentity(env = process.env, cwd = env.PWD || processCwd(), pid = process.pid) {
2086
- const sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim() || env.CLAUDE_PEER_ID?.trim() || `claude-${pid}-${shortHash(cwd)}`;
2197
+ let sessionId = env.CLAUDE_INTERCOM_SESSION_ID?.trim() || env.CLAUDE_PEER_ID?.trim();
2198
+ if (!sessionId && env.AGENT_INTERCOM_SESSION_ID !== void 0) {
2199
+ const raw = env.AGENT_INTERCOM_SESSION_ID.trim();
2200
+ if (raw) {
2201
+ if (!GENERIC_SESSION_ID_REGEX.test(raw)) {
2202
+ throw new Error("Invalid AGENT_INTERCOM_SESSION_ID: must match ^[A-Za-z0-9_-]{1,128}$");
2203
+ }
2204
+ sessionId = raw;
2205
+ }
2206
+ }
2207
+ if (!sessionId) {
2208
+ sessionId = `claude-${pid}-${shortHash(cwd)}`;
2209
+ }
2087
2210
  const cwdName = basename(cwd) || "workspace";
2088
- const name = env.CLAUDE_INTERCOM_NAME?.trim() || env.CLAUDE_PEER_NAME?.trim() || `claude-${cwdName}-${pid}`;
2211
+ 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
2212
  return {
2090
2213
  sessionId,
2091
2214
  name,
@@ -2381,6 +2381,7 @@ function loadConfig() {
2381
2381
 
2382
2382
  // claude/team.ts
2383
2383
  import { PARTICIPANT_STATES, TERMINAL_PARTICIPANT_STATES } from "@ctliz/agent-intercom-core/boss";
2384
+ import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
2384
2385
  var BOSS_NONTERMINAL_STATES = new Set(
2385
2386
  PARTICIPANT_STATES.filter((state) => !TERMINAL_PARTICIPANT_STATES.includes(state))
2386
2387
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctliz/agent-intercom-claude",
3
- "version": "0.12.0-connect.3",
3
+ "version": "0.13.0-connect.1",
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.1.0"
70
+ "@ctliz/agent-intercom-core": "0.2.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@ctliz/agent-intercom-core": "git+https://github.com/ctliz/agent-intercom-core.git#37e074970e2a9de32a16fc325607c3b476b0bd45",
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.1.0",
9
- commit: "37e074970e2a9de32a16fc325607c3b476b0bd45",
10
- integrity: "sha512-b5MXnfKh/RCjKoVor0tsFO+NKAqPROWgkgbHt7jiHxhdVbSh7gWvOdnVUtpSdPwJLYKSQrYXT+TJl7QocdJEPA==",
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 = `git+https://github.com/ctliz/agent-intercom-core.git#${APPROVED_CORE.commit}`;
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, devSpec, "supplied Core commit");
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 verifyPublishedProvenance() {
45
- const result = spawnSync("npm", [
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
57
  ], { encoding: "utf8" });
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
- const published = JSON.parse(result.stdout);
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 = "git+https://github.com/ctliz/agent-intercom-core.git#37e074970e2a9de32a16fc325607c3b476b0bd45";
9
+ const coreDevSpec = "0.2.0";
10
10
  const coreConsumers = new Set([
11
11
  "broker.mjs",
12
12
  "cci.mjs",
@@ -72,10 +72,10 @@ try {
72
72
  const manifestEntry = entries.get("package/package.json");
73
73
  if (!manifestEntry) throw new Error("Packed adapter is missing package.json");
74
74
  const manifest = JSON.parse(manifestEntry.toString("utf8"));
75
- if (manifest.peerDependencies?.[corePackage] !== "0.1.0") throw new Error("Packed adapter must require Core peer 0.1.0");
75
+ if (manifest.peerDependencies?.[corePackage] !== "0.2.0") throw new Error("Packed adapter must require Core peer 0.2.0");
76
76
  if (manifest.peerDependenciesMeta?.[corePackage]?.optional === true) throw new Error("Packed Core peer must not be optional");
77
77
  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 build commit");
78
+ if (manifest.devDependencies?.[corePackage] !== coreDevSpec) throw new Error("Packed adapter lost the approved Core dependency");
79
79
  if ([...entries.keys()].some((path) => path.includes("node_modules/@ctliz/agent-intercom-core/"))) {
80
80
  throw new Error("Packed adapter contains a private Core module tree");
81
81
  }
@@ -134,7 +134,7 @@ try {
134
134
  }
135
135
  }
136
136
 
137
- console.log(`Verified complete plugin+MCP+monitors+dist chain and ${bundles.length} packed bundles using Core peer 0.1.0`);
137
+ console.log(`Verified complete plugin+MCP+monitors+dist chain and ${bundles.length} packed bundles using Core peer 0.2.0`);
138
138
  } finally {
139
139
  if (temp) rmSync(temp, { recursive: true, force: true });
140
140
  }