@ctliz/agent-intercom-codex 0.11.0-connect.2 → 0.12.0-connect.2

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/broker/spawn.ts CHANGED
@@ -178,6 +178,7 @@ export function getBrokerSpawnOptions(
178
178
  cwd: string;
179
179
  env: NodeJS.ProcessEnv;
180
180
  windowsHide: true;
181
+ shell: false;
181
182
  } {
182
183
  return {
183
184
  detached: true,
@@ -185,6 +186,7 @@ export function getBrokerSpawnOptions(
185
186
  cwd: extensionDir,
186
187
  env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
187
188
  windowsHide: true,
189
+ shell: false,
188
190
  };
189
191
  }
190
192
 
@@ -109,6 +109,7 @@ export class CodexAppServerClient extends EventEmitter {
109
109
  encoding: "utf8",
110
110
  env: this.options.env,
111
111
  stdio: ["ignore", "pipe", "pipe"],
112
+ shell: false,
112
113
  });
113
114
  if (started.status !== 0) {
114
115
  throw new Error(`Failed to start Codex app-server daemon: ${started.stderr || started.stdout || `exit ${started.status}`}`);
@@ -124,6 +125,7 @@ export class CodexAppServerClient extends EventEmitter {
124
125
  const proc = spawn(this.options.command, this.options.args, {
125
126
  stdio: ["pipe", "pipe", "pipe"],
126
127
  env: this.options.env,
128
+ shell: false,
127
129
  });
128
130
  this.proc = proc;
129
131
  this.rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
@@ -34,7 +34,7 @@ function runDetachedClipboardCommand(
34
34
  const timeout = setTimeout(() => finish({ ok: false, error: `${command} did not start within ${timeoutMs}ms` }), timeoutMs);
35
35
 
36
36
  try {
37
- const proc = spawn(command, args, { env, stdio: ["pipe", "ignore", "ignore"] });
37
+ const proc = spawn(command, args, { env, stdio: ["pipe", "ignore", "ignore"], shell: false });
38
38
  proc.once("error", (error) => finish({ ok: false, error: error.message }));
39
39
  proc.once("spawn", () => {
40
40
  proc.stdin.on("error", () => {
@@ -75,7 +75,7 @@ function runClipboardCommand(
75
75
  let proc: ReturnType<typeof spawn> | undefined;
76
76
 
77
77
  try {
78
- proc = spawn(command, args, { env, stdio: ["pipe", "ignore", "pipe"] });
78
+ proc = spawn(command, args, { env, stdio: ["pipe", "ignore", "pipe"], shell: false });
79
79
  proc.stderr?.setEncoding("utf8");
80
80
  proc.stderr?.on("data", (chunk: string) => {
81
81
  if (stderr.length < 4096) stderr += chunk;
package/codex/coi.ts CHANGED
@@ -98,6 +98,7 @@ function gitString(cwd: string, args: string[]): string | null {
98
98
  cwd,
99
99
  encoding: "utf8",
100
100
  stdio: ["ignore", "pipe", "ignore"],
101
+ shell: false,
101
102
  });
102
103
  if (result.status !== 0) return null;
103
104
  const trimmed = result.stdout.trim();
@@ -499,7 +500,7 @@ export async function runInteractiveTui(
499
500
  // cannot reintroduce an ambient-PATH spawn after the initial preflight.
500
501
  assertHardenedBossProviderAuthority(protectedBossClient);
501
502
  const runInherited = async (): Promise<number> => {
502
- const tui = spawn(command, args, { cwd, env: launchEnv, stdio: "inherit" });
503
+ const tui = spawn(command, args, { cwd, env: launchEnv, stdio: "inherit", shell: false });
503
504
  const [code, signal] = await once(tui, "exit") as [number | null, NodeJS.Signals | null];
504
505
  if (typeof code === "number") return code;
505
506
  return signal === "SIGINT" ? 130 : 1;
@@ -634,6 +635,7 @@ export async function runCoi(options: CoiOptions, env: NodeJS.ProcessEnv = proce
634
635
  cwd: options.cwd,
635
636
  env,
636
637
  stdio: "inherit",
638
+ shell: false,
637
639
  });
638
640
  const [code, signal] = await once(help, "exit") as [number | null, NodeJS.Signals | null];
639
641
  if (typeof code === "number") return code;
@@ -669,6 +671,7 @@ export async function runCoi(options: CoiOptions, env: NodeJS.ProcessEnv = proce
669
671
  cwd: options.cwd,
670
672
  env,
671
673
  stdio: ["ignore", "ignore", "pipe"],
674
+ shell: false,
672
675
  });
673
676
  appServer.stderr?.on("data", (chunk) => {
674
677
  if (env.CODEX_INTERCOM_DEBUG) process.stderr.write(String(chunk));
@@ -16,6 +16,12 @@ interface ToolDefinition {
16
16
  name: string;
17
17
  description: string;
18
18
  inputSchema: Record<string, unknown>;
19
+ annotations: {
20
+ readOnlyHint: boolean;
21
+ destructiveHint: boolean;
22
+ idempotentHint: boolean;
23
+ openWorldHint: boolean;
24
+ };
19
25
  handler: ToolHandler;
20
26
  }
21
27
 
@@ -74,18 +80,21 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
74
80
  name: "intercom_whoami",
75
81
  description: "Return this Codex session's intercom identity for reliable targeting.",
76
82
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
83
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
77
84
  handler: async () => runtime.whoami(),
78
85
  },
79
86
  {
80
87
  name: "intercom_team",
81
88
  description: "Show your current manager and the live coworkers owned by that manager. No arguments are required.",
82
89
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
90
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
83
91
  handler: async () => runtime.team(),
84
92
  },
85
93
  {
86
94
  name: "intercom_status",
87
95
  description: "Show intercom connection status, active sessions, unread messages, and pending asks.",
88
96
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
97
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
89
98
  handler: async () => runtime.status(),
90
99
  },
91
100
  {
@@ -99,6 +108,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
99
108
  },
100
109
  additionalProperties: false,
101
110
  },
111
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
102
112
  handler: async (args) => runtime.list(
103
113
  args.scope === "directory" || args.scope === "repo" ? args.scope : "machine",
104
114
  asBoolean(args.include_self, false),
@@ -113,6 +123,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
113
123
  required: ["summary"],
114
124
  additionalProperties: false,
115
125
  },
126
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
116
127
  handler: async (args) => runtime.setSummary(asString(args.summary, "summary")),
117
128
  },
118
129
  {
@@ -128,6 +139,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
128
139
  required: ["to", "message"],
129
140
  additionalProperties: false,
130
141
  },
142
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
131
143
  handler: async (args) => runtime.send(asString(args.to, "to"), asString(args.message, "message"), asAttachmentArray(args.attachments)),
132
144
  },
133
145
  {
@@ -144,6 +156,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
144
156
  required: ["to", "message"],
145
157
  additionalProperties: false,
146
158
  },
159
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
147
160
  handler: async (args, signal) => runtime.ask(
148
161
  asString(args.to, "to"),
149
162
  asString(args.message, "message"),
@@ -160,6 +173,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
160
173
  properties: { mark_read: { type: "boolean", default: false } },
161
174
  additionalProperties: false,
162
175
  },
176
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
163
177
  handler: async (args) => runtime.pending(asBoolean(args.mark_read, false)),
164
178
  },
165
179
  {
@@ -175,6 +189,7 @@ export function buildToolDefinitions(runtime: CodexIntercomRuntime): ToolDefinit
175
189
  required: ["message"],
176
190
  additionalProperties: false,
177
191
  },
192
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
178
193
  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),
179
194
  },
180
195
  ];
@@ -217,7 +232,7 @@ export async function handleMcpRequest(request: JsonRpcRequest, runtime: CodexIn
217
232
  return ok(request.id, {});
218
233
  case "tools/list":
219
234
  return ok(request.id, {
220
- tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })),
235
+ tools: tools.map(({ name, description, inputSchema, annotations }) => ({ name, description, inputSchema, annotations })),
221
236
  });
222
237
  case "tools/call": {
223
238
  const name = request.params?.name;
package/codex/runtime.ts CHANGED
@@ -95,13 +95,28 @@ function shortHash(value: string): string {
95
95
  return createHash("sha256").update(value).digest("hex").slice(0, 8);
96
96
  }
97
97
 
98
+ const GENERIC_SESSION_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
99
+
98
100
  export function buildCodexRuntimeIdentity(env: NodeJS.ProcessEnv = process.env, cwd = env.PWD || processCwd(), pid = process.pid): CodexRuntimeIdentity {
99
- const sessionId = env.CODEX_INTERCOM_SESSION_ID?.trim()
100
- || env.CODEX_PEER_ID?.trim()
101
- || `codex-${pid}-${shortHash(cwd)}`;
101
+ let sessionId = env.CODEX_INTERCOM_SESSION_ID?.trim()
102
+ || env.CODEX_PEER_ID?.trim();
103
+ if (!sessionId && env.AGENT_INTERCOM_SESSION_ID !== undefined) {
104
+ const raw = env.AGENT_INTERCOM_SESSION_ID.trim();
105
+ if (raw) {
106
+ if (!GENERIC_SESSION_ID_REGEX.test(raw)) {
107
+ throw new Error("Invalid AGENT_INTERCOM_SESSION_ID: must match ^[A-Za-z0-9_-]{1,128}$");
108
+ }
109
+ sessionId = raw;
110
+ }
111
+ }
112
+ if (!sessionId) {
113
+ sessionId = `codex-${pid}-${shortHash(cwd)}`;
114
+ }
115
+
102
116
  const cwdName = basename(cwd) || "workspace";
103
117
  const name = env.CODEX_INTERCOM_NAME?.trim()
104
118
  || env.CODEX_PEER_NAME?.trim()
119
+ || (env.AGENT_INTERCOM_SESSION_NAME !== undefined && env.AGENT_INTERCOM_SESSION_NAME.trim() ? env.AGENT_INTERCOM_SESSION_NAME.trim() : undefined)
105
120
  || `codex-${cwdName}-${pid}`;
106
121
  return {
107
122
  sessionId,
@@ -167,6 +182,7 @@ export function detectGitRoot(cwd: string): string | null {
167
182
  cwd,
168
183
  encoding: "utf8",
169
184
  stdio: ["ignore", "pipe", "ignore"],
185
+ shell: false,
170
186
  });
171
187
  if (result.status !== 0) return null;
172
188
  return result.stdout.trim() || null;
package/codex/team.ts CHANGED
@@ -8,10 +8,15 @@ import {
8
8
  workerIdentityFromEnvironment,
9
9
  type WorkerIdentityV2,
10
10
  } from "@ctliz/agent-intercom-core/boss";
11
+ import { readTeamManifestAsync, TeamManifestError } from "@ctliz/agent-intercom-core/team-manifest";
12
+
13
+ export type TeamSource = "orchestrator" | "boss" | "manifest" | "live" | "standalone";
11
14
 
12
15
  export interface TeamSession {
13
16
  id: string;
14
17
  name?: string;
18
+ model?: string;
19
+ origin?: "local" | "remote";
15
20
  boss?: {
16
21
  binding?: {
17
22
  bossRunId?: unknown;
@@ -43,8 +48,22 @@ interface StoredWorker {
43
48
  canonicalIdentity?: WorkerIdentityV2;
44
49
  }
45
50
 
46
- export interface TeamMember { id: string; target: string; harness?: string; role?: string; state?: string; connected: boolean; }
47
- export interface IntercomTeam { teamId?: string; self: { id: string; workerId?: string; isManager: boolean }; manager?: { target: string; connected: boolean }; coworkers: TeamMember[]; }
51
+ export interface TeamMember {
52
+ id: string;
53
+ target: string;
54
+ harness?: string;
55
+ role?: string;
56
+ state?: string;
57
+ connected: boolean;
58
+ }
59
+
60
+ export interface IntercomTeam {
61
+ teamId?: string;
62
+ self: { id: string; workerId?: string; isManager: boolean };
63
+ manager?: { target: string; connected: boolean };
64
+ coworkers: TeamMember[];
65
+ source: TeamSource;
66
+ }
48
67
 
49
68
  const LEGACY_LIVE_STATES = new Set(["provisioning", "running", "idle", "needs_attention", "stopping"]);
50
69
  const CANONICAL_LIVE_STATES = new Set(["provisioning", "registering", "ready", "working", "waiting", "paused", "stalled", "blocked", "unreachable"]);
@@ -54,12 +73,26 @@ const connectedTo = (sessions: TeamSession[], target: string): boolean => {
54
73
  return sessions.some((session) => session.id === target || session.name?.toLowerCase() === normalized);
55
74
  };
56
75
 
76
+ function hasBossEnvironmentKeys(env: NodeJS.ProcessEnv): boolean {
77
+ const bossKeys = [
78
+ "AGENT_INTERCOM_BOSS_RUN_ID",
79
+ "AGENT_INTERCOM_PARTICIPANT_ID",
80
+ "AGENT_INTERCOM_BINDING_EPOCH",
81
+ "AGENT_INTERCOM_WORKER_INCARNATION_ID",
82
+ "AGENT_INTERCOM_WORKER_GENERATION",
83
+ ] as const;
84
+ return bossKeys.some((key) => env[key] !== undefined);
85
+ }
86
+
57
87
  function bossIdentityFromEnvironment(env: NodeJS.ProcessEnv): WorkerIdentityV2 | undefined {
58
- const bossKeys = ["AGENT_INTERCOM_BOSS_RUN_ID", "AGENT_INTERCOM_PARTICIPANT_ID", "AGENT_INTERCOM_BINDING_EPOCH"] as const;
59
- if (!bossKeys.some((key) => env[key] !== undefined)) return undefined;
60
- const identity = workerIdentityFromEnvironment(env);
61
- if (!("bossRunId" in identity)) throw new Error("Incomplete Boss worker identity cannot discover a team");
62
- return identity;
88
+ if (!hasBossEnvironmentKeys(env)) return undefined;
89
+ try {
90
+ const identity = workerIdentityFromEnvironment(env);
91
+ if (!("bossRunId" in identity)) return undefined;
92
+ return identity;
93
+ } catch {
94
+ return undefined;
95
+ }
63
96
  }
64
97
 
65
98
  function canonicalWorker(value: unknown): StoredWorker {
@@ -133,17 +166,123 @@ async function readWorkers(agentDir: string): Promise<{ version: 1 | 2; workers:
133
166
  }
134
167
  }
135
168
 
169
+ async function resolveNonAuthoritativeTeam(
170
+ input: { selfId: string; sessions: TeamSession[] },
171
+ env: NodeJS.ProcessEnv,
172
+ ): Promise<IntercomTeam> {
173
+ if (env.AGENT_INTERCOM_TEAM_MANIFEST !== undefined) {
174
+ const rawPath = env.AGENT_INTERCOM_TEAM_MANIFEST.trim();
175
+ if (!rawPath) {
176
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
177
+ }
178
+ const manifest = await readTeamManifestAsync(rawPath);
179
+ if (!manifest) {
180
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
181
+ }
182
+ const selfMember = manifest.members.find((m) => m.sessionId === input.selfId);
183
+ if (!selfMember) {
184
+ throw new TeamManifestError("ERR_TEAM_MANIFEST_INVALID");
185
+ }
186
+
187
+ const isManager = input.selfId === manifest.leadId;
188
+ const managerTarget = manifest.leadId;
189
+ const managerConnected = connectedTo(input.sessions, managerTarget);
190
+
191
+ const coworkers: TeamMember[] = manifest.members
192
+ .filter((m) => m.sessionId !== input.selfId)
193
+ .filter((m) => isManager || m.sessionId !== managerTarget)
194
+ .map((m) => {
195
+ const live = input.sessions.find((s) => s.id === m.sessionId || s.name === m.sessionId);
196
+ return {
197
+ id: m.sessionId,
198
+ target: m.sessionId,
199
+ role: m.role,
200
+ connected: Boolean(live),
201
+ };
202
+ });
203
+
204
+ return {
205
+ teamId: manifest.runId,
206
+ self: { id: input.selfId, isManager },
207
+ manager: { target: managerTarget, connected: managerConnected },
208
+ coworkers,
209
+ source: "manifest",
210
+ };
211
+ }
212
+
213
+ if (stringValue(env.AGENT_INTERCOM_SCOPE_ID)) {
214
+ const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
215
+ ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
216
+ const role = stringValue(env.AGENT_INTERCOM_ROLE)?.toLowerCase();
217
+
218
+ const isManager = role === "manager"
219
+ || (managerTarget !== undefined && managerTarget === input.selfId)
220
+ || (managerTarget === undefined && role !== "worker");
221
+
222
+ const effectiveManagerTarget = isManager ? input.selfId : managerTarget;
223
+
224
+ const coworkers: TeamMember[] = input.sessions
225
+ .filter((session) => session.id !== input.selfId)
226
+ .filter((session) => session.model !== "human")
227
+ .filter((session) => session.id !== effectiveManagerTarget)
228
+ .map((session): TeamMember => ({
229
+ id: session.id,
230
+ target: session.id,
231
+ ...(session.model ? { harness: session.model } : {}),
232
+ connected: true,
233
+ }));
234
+
235
+ const manager = effectiveManagerTarget
236
+ ? {
237
+ target: effectiveManagerTarget,
238
+ connected: isManager ? true : connectedTo(input.sessions, effectiveManagerTarget),
239
+ }
240
+ : undefined;
241
+
242
+ return {
243
+ teamId: effectiveManagerTarget ?? input.selfId,
244
+ self: { id: input.selfId, isManager },
245
+ ...(manager ? { manager } : {}),
246
+ coworkers,
247
+ source: "live",
248
+ };
249
+ }
250
+
251
+ const managerTarget = stringValue(env.AGENT_INTERCOM_MANAGER_TARGET)
252
+ ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
253
+ return {
254
+ teamId: managerTarget ?? input.selfId,
255
+ self: { id: input.selfId, isManager: !managerTarget },
256
+ manager: managerTarget
257
+ ? { target: managerTarget, connected: connectedTo(input.sessions, managerTarget) }
258
+ : { target: input.selfId, connected: true },
259
+ coworkers: [],
260
+ source: "standalone",
261
+ };
262
+ }
263
+
136
264
  export async function resolveIntercomTeam(input: { selfId: string; sessions: TeamSession[]; env?: NodeJS.ProcessEnv; agentDir?: string }): Promise<IntercomTeam> {
137
265
  const env = input.env ?? process.env;
138
266
  const snapshot = await readWorkers(input.agentDir ?? getAgentDirPath());
139
267
  const workers = snapshot.workers;
140
268
  const workerId = stringValue(env.AGENT_INTERCOM_WORKER_ID);
269
+ const selfSession = input.sessions.find((session) => session.id === input.selfId);
270
+ const isBossContext = selfSession?.boss !== undefined || hasBossEnvironmentKeys(env);
141
271
  const bossIdentity = bossIdentityFromEnvironment(env);
142
272
  const runId = stringValue(env.AGENT_INTERCOM_RUN_ID);
273
+
274
+ if (isBossContext && bossIdentity === undefined) {
275
+ return {
276
+ self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
277
+ coworkers: [],
278
+ source: "boss",
279
+ };
280
+ }
281
+
143
282
  const currentMatches = workerId ? workers.filter((worker) => (
144
283
  stringValue(worker.id) === workerId
145
284
  && (bossIdentity === undefined
146
- ? (!runId || stringValue(worker.runId) === runId)
285
+ ? (snapshot.version === 1 && (!runId || stringValue(worker.runId) === runId))
147
286
  : snapshot.version === 2
148
287
  && worker.canonicalIdentity?.workerId === bossIdentity.workerId
149
288
  && worker.canonicalIdentity.workerIncarnationId === bossIdentity.workerIncarnationId
@@ -166,13 +305,69 @@ export async function resolveIntercomTeam(input: { selfId: string; sessions: Tea
166
305
  // projection. A substituted self ID, ambiguous ID/target, or stale binding
167
306
  // never unlocks a roster assembled from ambient same-run records.
168
307
  if (bossIdentity !== undefined && !exactCurrentProjection) {
169
- return { self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false }, coworkers: [] };
308
+ return { self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false }, coworkers: [], source: "boss" };
309
+ }
310
+
311
+ if (bossIdentity === undefined && !current) {
312
+ if (workerId === undefined && snapshot.version === 1) {
313
+ const ownedCoworkers = workers
314
+ .filter((worker) => worker.owned === true)
315
+ .filter((worker) => {
316
+ const mgr = stringValue(worker.managerSessionId);
317
+ return mgr !== undefined && mgr === input.selfId;
318
+ })
319
+ .filter((worker) => LEGACY_LIVE_STATES.has(stringValue(worker.state) ?? ""))
320
+ .filter((worker) => stringValue(worker.id) !== input.selfId)
321
+ .map((worker): TeamMember | undefined => {
322
+ const id = stringValue(worker.id);
323
+ if (!id) return undefined;
324
+ const target = stringValue(worker.intercomTarget) ?? id;
325
+ return {
326
+ id,
327
+ target,
328
+ ...(stringValue(worker.harness) ? { harness: stringValue(worker.harness) } : {}),
329
+ ...(stringValue(worker.role) ? { role: stringValue(worker.role) } : {}),
330
+ ...(stringValue(worker.state) ? { state: stringValue(worker.state) } : {}),
331
+ connected: connectedTo(input.sessions, target),
332
+ };
333
+ })
334
+ .filter((member): member is TeamMember => Boolean(member));
335
+
336
+ if (ownedCoworkers.length > 0) {
337
+ return {
338
+ teamId: input.selfId,
339
+ self: { id: input.selfId, isManager: true },
340
+ manager: { target: input.selfId, connected: true },
341
+ coworkers: ownedCoworkers,
342
+ source: "orchestrator",
343
+ };
344
+ }
345
+ }
346
+
347
+ return resolveNonAuthoritativeTeam(input, env);
170
348
  }
171
349
 
172
- const managerTarget = stringValue(current?.managerSessionId)
173
- ?? (bossIdentity === undefined ? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID) : undefined);
174
- const teamId = managerTarget ?? input.selfId;
175
350
  const currentRole = stringValue(current?.role);
351
+ const selfIsManager = bossIdentity !== undefined
352
+ ? currentRole === "manager"
353
+ : undefined;
354
+
355
+ const managerTarget = bossIdentity !== undefined
356
+ ? (selfIsManager ? input.selfId : stringValue(current?.managerSessionId))
357
+ : stringValue(current?.managerSessionId) ?? stringValue(env.AGENT_INTERCOM_MANAGER_TARGET) ?? stringValue(env.AGENT_INTERCOM_MANAGER_SESSION_ID);
358
+
359
+ if (bossIdentity === undefined && !managerTarget) {
360
+ return {
361
+ self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: false },
362
+ coworkers: [],
363
+ source: "orchestrator",
364
+ };
365
+ }
366
+
367
+ const teamId = bossIdentity !== undefined
368
+ ? ("bossRunId" in bossIdentity ? bossIdentity.bossRunId : managerTarget ?? input.selfId)
369
+ : (managerTarget ?? input.selfId);
370
+
176
371
  const canDiscoverOwnedRoster = bossIdentity === undefined || currentRole === "manager" || currentRole === "controller";
177
372
  const coworkers = (canDiscoverOwnedRoster ? workers : []).filter((worker) => worker.owned === true)
178
373
  .filter((worker) => bossIdentity === undefined || (
@@ -182,10 +377,10 @@ export async function resolveIntercomTeam(input: { selfId: string; sessions: Tea
182
377
  && "bossRunId" in bossIdentity
183
378
  && worker.canonicalIdentity.bossRunId === bossIdentity.bossRunId
184
379
  ))
185
- .filter((worker) => stringValue(worker.managerSessionId) === teamId)
380
+ .filter((worker) => stringValue(worker.managerSessionId) === (bossIdentity !== undefined ? managerTarget : teamId))
186
381
  .filter((worker) => stringValue(worker.intercomTarget) !== managerTarget)
187
382
  .filter((worker) => (snapshot.version === 2 ? CANONICAL_LIVE_STATES : LEGACY_LIVE_STATES).has(stringValue(worker.state) ?? ""))
188
- .filter((worker) => stringValue(worker.id) !== workerId)
383
+ .filter((worker) => stringValue(worker.id) !== workerId && stringValue(worker.id) !== input.selfId)
189
384
  .map((worker): TeamMember | undefined => {
190
385
  const id = stringValue(worker.id);
191
386
  if (!id) return undefined;
@@ -222,16 +417,48 @@ export async function resolveIntercomTeam(input: { selfId: string; sessions: Tea
222
417
  : bossIdentity === undefined
223
418
  ? connectedTo(input.sessions, managerTarget)
224
419
  : managerWorker !== undefined && exactBossRosterSession(input.sessions, managerWorker) !== undefined;
420
+
421
+ const isManager = bossIdentity !== undefined
422
+ ? selfIsManager!
423
+ : false;
424
+
225
425
  return {
226
426
  teamId,
227
- self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager: bossIdentity === undefined && !managerTarget },
427
+ self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager },
228
428
  ...(managerTarget
229
- ? { manager: { target: managerTarget, connected: managerConnected } }
230
- : bossIdentity === undefined ? { manager: { target: input.selfId, connected: true } } : {}),
429
+ ? { manager: { target: managerTarget, connected: isManager ? true : managerConnected } }
430
+ : {}),
231
431
  coworkers,
432
+ source: bossIdentity !== undefined ? "boss" : "orchestrator",
232
433
  };
233
434
  }
234
435
 
436
+ /** Authorizes a read-only local inbox lookup using exact orchestrator ownership. */
437
+ export function resolveManagedInboxSession(input: {
438
+ team: IntercomTeam;
439
+ sessions: TeamSession[];
440
+ requestedSession: string;
441
+ }): TeamSession {
442
+ if (input.team.source !== "orchestrator" && input.team.source !== "boss") {
443
+ throw new Error("Pending-ask inbox access denied: cross-session inbox inspection is only permitted for Orchestrator/Boss-managed teams");
444
+ }
445
+ if (!input.team.self.isManager) {
446
+ throw new Error("Only a manager may inspect another session's pending-ask inbox");
447
+ }
448
+ const member = input.team.coworkers.find((entry) => entry.target === input.requestedSession);
449
+ if (!member) {
450
+ throw new Error(`Pending-ask inbox access denied for "${input.requestedSession}"; select an owned coworker target returned by intercom_team`);
451
+ }
452
+ const liveSession = input.sessions.find((session) => session.id === input.requestedSession);
453
+ if (!liveSession) {
454
+ throw new Error(`Pending-ask inbox access denied for "${input.requestedSession}"; the owned coworker target must equal an exact connected stable session ID`);
455
+ }
456
+ if (liveSession.origin === "remote") {
457
+ throw new Error(`Pending-ask inbox "${input.requestedSession}" is remote and cannot be read from this host`);
458
+ }
459
+ return liveSession;
460
+ }
461
+
235
462
  export function formatIntercomTeam(team: IntercomTeam): string {
236
463
  const lines = [
237
464
  `Manager: ${team.manager ? `${team.manager.target} [${team.manager.connected ? "connected" : "not connected"}]` : "unknown"}`,