@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 +18 -3
- package/claude/team.ts +210 -31
- package/dist/cci.mjs +1 -0
- package/dist/ccim.mjs +1 -0
- package/dist/claude-server.mjs +148 -25
- package/dist/worker-daemon.mjs +1 -0
- package/package.json +3 -3
- package/scripts/verify-core-provenance.mjs +16 -14
- package/scripts/verify-packed-runtime.mjs +4 -4
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,
|
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/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
|
);
|
package/dist/claude-server.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
-
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
-
|
|
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,
|
package/dist/worker-daemon.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
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
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
|
-
|
|
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",
|
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
}
|