@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 +2 -0
- package/codex/app-server-client.ts +2 -0
- package/codex/clipboard.ts +2 -2
- package/codex/coi.ts +4 -1
- package/codex/mcp-protocol.ts +16 -1
- package/codex/runtime.ts +19 -3
- package/codex/team.ts +244 -17
- package/dist/bridge-daemon.mjs +149 -17
- package/dist/broker.mjs +1 -1
- package/dist/build-info.json +2 -2
- package/dist/codex-server.mjs +172 -19
- package/dist/coi.mjs +158 -23
- package/package.json +3 -3
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 });
|
package/codex/clipboard.ts
CHANGED
|
@@ -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));
|
package/codex/mcp-protocol.ts
CHANGED
|
@@ -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
|
-
|
|
100
|
-
|| env.CODEX_PEER_ID?.trim()
|
|
101
|
-
|
|
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 {
|
|
47
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
427
|
+
self: { id: input.selfId, ...(workerId ? { workerId } : {}), isManager },
|
|
228
428
|
...(managerTarget
|
|
229
|
-
? { manager: { target: managerTarget, connected: managerConnected } }
|
|
230
|
-
:
|
|
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"}`,
|