@ctliz/agent-intercom-codex 0.11.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/.codex-plugin/plugin.json +39 -0
- package/.mcp.json +10 -0
- package/COPYRIGHT +6 -0
- package/LICENSE +661 -0
- package/LICENSE_TRANSITION.md +25 -0
- package/README.md +572 -0
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/assets/logo-generated.png +0 -0
- package/assets/logo.svg +22 -0
- package/boss-control-outbox.ts +195 -0
- package/broker/access-credential.ts +76 -0
- package/broker/access-registry.ts +464 -0
- package/broker/audit.ts +60 -0
- package/broker/authorization.ts +54 -0
- package/broker/boss-adapter.ts +531 -0
- package/broker/boss-control-ledger.ts +306 -0
- package/broker/broker.ts +2327 -0
- package/broker/client.ts +1111 -0
- package/broker/framing.ts +87 -0
- package/broker/ownership.ts +61 -0
- package/broker/paths.ts +161 -0
- package/broker/spawn.ts +453 -0
- package/codex/app-server-client.ts +562 -0
- package/codex/boss-client.ts +34 -0
- package/codex/bridge-config.ts +282 -0
- package/codex/bridge-daemon.ts +913 -0
- package/codex/clipboard.ts +142 -0
- package/codex/coi.ts +802 -0
- package/codex/contact.ts +54 -0
- package/codex/mcp-protocol.ts +248 -0
- package/codex/runtime.ts +488 -0
- package/codex/server.ts +60 -0
- package/codex/team.ts +249 -0
- package/codex/tui-input.ts +174 -0
- package/config.ts +195 -0
- package/dist/bridge-daemon.mjs +3781 -0
- package/dist/broker.mjs +3012 -0
- package/dist/build-info.json +12 -0
- package/dist/codex-server.mjs +2890 -0
- package/dist/coi.mjs +4686 -0
- package/durable-json.ts +25 -0
- package/licenses/MIT-pi-intercom.txt +21 -0
- package/outbound-outbox.ts +116 -0
- package/package.json +89 -0
- package/protocol-v4/contract.ts +27 -0
- package/provider/protected-service.ts +162 -0
- package/provider/provider.mjs +34 -0
- package/scripts/build-info.mjs +58 -0
- package/scripts/build.mjs +55 -0
- package/skills/codex-intercom/SKILL.md +104 -0
- package/tsconfig.json +16 -0
- package/types.ts +225 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, parse as parsePath, resolve } from "node:path";
|
|
3
|
+
import { cwd as processCwd } from "node:process";
|
|
4
|
+
import { types as nodeUtilTypes } from "node:util";
|
|
5
|
+
import { getIntercomDirPath, restrictIntercomRuntimeFile } from "../broker/paths.ts";
|
|
6
|
+
import { HARDENED_BOSS_CODEX_DEFAULTS, type HardenedBossClientKind } from "./boss-client.ts";
|
|
7
|
+
import { assertBossCanonicalData } from "../broker/boss-adapter.ts";
|
|
8
|
+
|
|
9
|
+
export interface BridgeAgentConfig {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
threadId?: string;
|
|
15
|
+
instructions?: string;
|
|
16
|
+
approvalPolicy?: unknown;
|
|
17
|
+
sandboxPolicy?: unknown;
|
|
18
|
+
bossClient?: HardenedBossClientKind;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BridgeConfig {
|
|
22
|
+
agents: BridgeAgentConfig[];
|
|
23
|
+
statePath: string;
|
|
24
|
+
appServer?: {
|
|
25
|
+
command?: string;
|
|
26
|
+
args?: string[];
|
|
27
|
+
transport?: "stdio" | "unix-websocket";
|
|
28
|
+
socketPath?: string;
|
|
29
|
+
startDaemon?: boolean;
|
|
30
|
+
startDaemonCommand?: string;
|
|
31
|
+
startDaemonArgs?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BridgeState {
|
|
36
|
+
agents: Record<string, { threadId: string; updatedAt: number }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DEFAULT_BRIDGE_CONFIG_PATH = join(getIntercomDirPath(), "codex-bridge.json");
|
|
40
|
+
export const DEFAULT_BRIDGE_STATE_PATH = join(getIntercomDirPath(), "codex-bridge-state.json");
|
|
41
|
+
|
|
42
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
43
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !nodeUtilTypes.isProxy(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function optionalString(value: unknown, field: string): string | undefined {
|
|
47
|
+
if (value === undefined || value === null) return undefined;
|
|
48
|
+
if (typeof value !== "string") throw new Error(`${field} must be a string`);
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
return trimmed || undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requireString(value: unknown, field: string): string {
|
|
54
|
+
const result = optionalString(value, field);
|
|
55
|
+
if (!result) throw new Error(`${field} must be a non-empty string`);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseHardenedBossClientKind(value: unknown, field: string): HardenedBossClientKind | undefined {
|
|
60
|
+
if (value === undefined || value === null) return undefined;
|
|
61
|
+
if (value === "boss_participant" || value === "boss_reviewer") return value;
|
|
62
|
+
throw new Error(`${field} must be boss_participant or boss_reviewer`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function sandboxType(value: unknown): string | undefined {
|
|
66
|
+
if (!isRecord(value)) return undefined;
|
|
67
|
+
return typeof value.type === "string" ? value.type : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function assertHardenedBossAgentConfig(agent: BridgeAgentConfig): void {
|
|
71
|
+
if (nodeUtilTypes.isProxy(agent)) throw new Error("Hardened Boss agent config must not be a proxy");
|
|
72
|
+
assertBossCanonicalData(agent, "$.agent");
|
|
73
|
+
if (agent.bossClient === undefined) return;
|
|
74
|
+
if (agent.sandboxPolicy !== undefined && !isRecord(agent.sandboxPolicy)) {
|
|
75
|
+
throw new Error(`${agent.bossClient} sandboxPolicy must be a plain object`);
|
|
76
|
+
}
|
|
77
|
+
if (agent.approvalPolicy !== undefined && typeof agent.approvalPolicy !== "string") {
|
|
78
|
+
throw new Error(`${agent.bossClient} approvalPolicy must be a string`);
|
|
79
|
+
}
|
|
80
|
+
const type = sandboxType(agent.sandboxPolicy);
|
|
81
|
+
if (type === "dangerFullAccess" || type === "danger-full-access") {
|
|
82
|
+
throw new Error(`${agent.bossClient} cannot use danger-full-access`);
|
|
83
|
+
}
|
|
84
|
+
if (agent.approvalPolicy === "never") {
|
|
85
|
+
throw new Error(`${agent.bossClient} cannot disable approval checks`);
|
|
86
|
+
}
|
|
87
|
+
if (agent.bossClient === "boss_reviewer" && type !== undefined && type !== "readOnly" && type !== "read-only") {
|
|
88
|
+
throw new Error("boss_reviewer must use a read-only sandbox");
|
|
89
|
+
}
|
|
90
|
+
const canonicalCwd = resolve(agent.cwd);
|
|
91
|
+
if (agent.bossClient === "boss_participant" && canonicalCwd === parsePath(canonicalCwd).root) {
|
|
92
|
+
throw new Error("boss_participant workspace root must not be a filesystem root");
|
|
93
|
+
}
|
|
94
|
+
if (isRecord(agent.sandboxPolicy)) {
|
|
95
|
+
assertBossCanonicalData(agent.sandboxPolicy, "$.agent.sandboxPolicy");
|
|
96
|
+
const allowedKeys = type === "workspaceWrite" || type === "workspace-write"
|
|
97
|
+
? new Set(["type", "writableRoots", "networkAccess"])
|
|
98
|
+
: new Set(["type", "networkAccess"]);
|
|
99
|
+
const keys = Reflect.ownKeys(agent.sandboxPolicy);
|
|
100
|
+
if (keys.some((key) => typeof key !== "string" || !allowedKeys.has(key))) {
|
|
101
|
+
throw new Error(`${agent.bossClient} sandboxPolicy contains unsupported capability fields`);
|
|
102
|
+
}
|
|
103
|
+
if (agent.sandboxPolicy.networkAccess !== false) {
|
|
104
|
+
throw new Error(`${agent.bossClient} networkAccess must be false`);
|
|
105
|
+
}
|
|
106
|
+
if (type !== "readOnly" && type !== "read-only" && type !== "workspaceWrite" && type !== "workspace-write") {
|
|
107
|
+
throw new Error(`${agent.bossClient} sandboxPolicy type is unsupported`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (isRecord(agent.sandboxPolicy) && (type === "workspaceWrite" || type === "workspace-write")) {
|
|
111
|
+
const roots = agent.sandboxPolicy.writableRoots;
|
|
112
|
+
assertBossCanonicalData(roots, "$.agent.sandboxPolicy.writableRoots");
|
|
113
|
+
if (!Array.isArray(roots) || nodeUtilTypes.isProxy(roots) || roots.some((root) => typeof root !== "string")) {
|
|
114
|
+
throw new Error(`${agent.bossClient} writableRoots must be a dense string array`);
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
agent.bossClient === "boss_reviewer"
|
|
118
|
+
|| roots.length !== 1
|
|
119
|
+
|| resolve(roots[0]) !== canonicalCwd
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(`${agent.bossClient} writable roots must be restricted to the agent cwd`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (agent.bossClient === "boss_participant") {
|
|
125
|
+
// This adapter has no protected broker/assignment projection carrying a
|
|
126
|
+
// canonical workspace root. Caller cwd/config/env values cannot supply
|
|
127
|
+
// that authority, so writable protected launches remain dormant.
|
|
128
|
+
throw new Error("boss_participant requires unavailable broker-owned assigned workspace authority");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function bridgeAgentApprovalPolicy(agent: BridgeAgentConfig): unknown {
|
|
133
|
+
return agent.approvalPolicy
|
|
134
|
+
?? (agent.bossClient === undefined ? "never" : HARDENED_BOSS_CODEX_DEFAULTS[agent.bossClient].approvalPolicy);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function bridgeAgentDefaultSandbox(agent: BridgeAgentConfig): "read-only" | "workspace-write" | undefined {
|
|
138
|
+
return agent.bossClient === undefined ? undefined : HARDENED_BOSS_CODEX_DEFAULTS[agent.bossClient].sandbox;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function assertHardenedBossBridgeConfig(config: BridgeConfig): void {
|
|
142
|
+
assertBossCanonicalData(config, "$.bridgeConfig");
|
|
143
|
+
if (nodeUtilTypes.isProxy(config) || nodeUtilTypes.isProxy(config.agents) || !Array.isArray(config.agents)) {
|
|
144
|
+
throw new Error("Bridge config and agents must be plain non-proxy data");
|
|
145
|
+
}
|
|
146
|
+
for (let index = 0; index < config.agents.length; index += 1) {
|
|
147
|
+
if (!Object.hasOwn(config.agents, index)) throw new Error("Bridge agents must not be sparse");
|
|
148
|
+
const agent = config.agents[index];
|
|
149
|
+
if (typeof agent !== "object" || agent === null || Array.isArray(agent) || nodeUtilTypes.isProxy(agent)) {
|
|
150
|
+
throw new Error("Bridge agents must be plain non-proxy objects");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!config.agents.some((agent) => agent.bossClient !== undefined)) return;
|
|
154
|
+
if (config.appServer !== undefined) {
|
|
155
|
+
assertBossCanonicalData(config.appServer, "$.appServer");
|
|
156
|
+
if (nodeUtilTypes.isProxy(config.appServer)) throw new Error("Hardened Boss app-server config must not be a proxy");
|
|
157
|
+
if (config.appServer.command !== undefined || config.appServer.startDaemonCommand !== undefined) {
|
|
158
|
+
throw new Error("Hardened Boss bridge cannot use caller-provided app-server commands");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const args of [config.appServer?.args, config.appServer?.startDaemonArgs]) {
|
|
162
|
+
if (!args) continue;
|
|
163
|
+
assertBossCanonicalData(args, "$.appServer.argv");
|
|
164
|
+
if (nodeUtilTypes.isProxy(args) || args.some((arg) => typeof arg !== "string")) throw new Error("Hardened Boss bridge arguments must be dense string arrays");
|
|
165
|
+
for (const arg of args) {
|
|
166
|
+
if (arg === "--") {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (arg.length > 2 && arg.startsWith("-C")) {
|
|
170
|
+
throw new Error("Hardened Boss bridge cannot pass launch escape -C to app-server");
|
|
171
|
+
}
|
|
172
|
+
const optionName = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
173
|
+
if (
|
|
174
|
+
["-c", "--config", "-p", "--profile", "--enable", "--disable"].includes(optionName)
|
|
175
|
+
|| (optionName.startsWith("-c") && optionName !== "-C")
|
|
176
|
+
|| optionName.startsWith("-p")
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(`Hardened Boss bridge cannot pass raw ${optionName} or profile configuration to app-server`);
|
|
179
|
+
}
|
|
180
|
+
if (["--dangerously-bypass-approvals-and-sandbox", "--dangerously-bypass-hook-trust", "--yolo", "--add-dir", "--cd", "-C"].includes(optionName)) {
|
|
181
|
+
throw new Error(`Hardened Boss bridge cannot pass launch escape ${optionName} to app-server`);
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
["--sandbox", "--ask-for-approval"].includes(optionName)
|
|
185
|
+
|| optionName === "-s"
|
|
186
|
+
|| optionName === "-a"
|
|
187
|
+
|| optionName.startsWith("-s")
|
|
188
|
+
|| optionName.startsWith("-a")
|
|
189
|
+
) {
|
|
190
|
+
throw new Error(`Hardened Boss bridge cannot pass policy override ${optionName} to app-server`);
|
|
191
|
+
}
|
|
192
|
+
if (optionName.startsWith("-") && /(?:yolo|danger|bypass)/i.test(optionName)) {
|
|
193
|
+
throw new Error(`Hardened Boss bridge cannot pass launch escape ${optionName} to app-server`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// The aggregate validator is itself a complete pre-spawn boundary; callers
|
|
198
|
+
// cannot validate argv while bypassing per-agent authority ceilings.
|
|
199
|
+
for (const agent of config.agents) assertHardenedBossAgentConfig(agent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizeAgent(raw: unknown, index: number): BridgeAgentConfig {
|
|
203
|
+
if (!isRecord(raw)) throw new Error(`agents[${index}] must be an object`);
|
|
204
|
+
const id = requireString(raw.id, `agents[${index}].id`);
|
|
205
|
+
const name = optionalString(raw.name, `agents[${index}].name`) ?? id;
|
|
206
|
+
const agent: BridgeAgentConfig = {
|
|
207
|
+
id,
|
|
208
|
+
name,
|
|
209
|
+
cwd: resolve(optionalString(raw.cwd, `agents[${index}].cwd`) ?? processCwd()),
|
|
210
|
+
model: optionalString(raw.model, `agents[${index}].model`),
|
|
211
|
+
threadId: optionalString(raw.threadId, `agents[${index}].threadId`),
|
|
212
|
+
instructions: optionalString(raw.instructions, `agents[${index}].instructions`),
|
|
213
|
+
approvalPolicy: raw.approvalPolicy,
|
|
214
|
+
sandboxPolicy: raw.sandboxPolicy,
|
|
215
|
+
bossClient: parseHardenedBossClientKind(raw.bossClient, `agents[${index}].bossClient`),
|
|
216
|
+
};
|
|
217
|
+
assertHardenedBossAgentConfig(agent);
|
|
218
|
+
return agent;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function defaultBridgeConfig(env: NodeJS.ProcessEnv = process.env): BridgeConfig {
|
|
222
|
+
const id = env.CODEX_INTERCOM_BRIDGE_ID?.trim() || "codex-worker";
|
|
223
|
+
const bossClient = parseHardenedBossClientKind(env.CODEX_INTERCOM_BOSS_CLIENT?.trim(), "CODEX_INTERCOM_BOSS_CLIENT");
|
|
224
|
+
const agent: BridgeAgentConfig = {
|
|
225
|
+
id,
|
|
226
|
+
name: env.CODEX_INTERCOM_BRIDGE_NAME?.trim() || id,
|
|
227
|
+
cwd: resolve(env.CODEX_INTERCOM_BRIDGE_CWD?.trim() || processCwd()),
|
|
228
|
+
model: env.CODEX_INTERCOM_BRIDGE_MODEL?.trim() || undefined,
|
|
229
|
+
instructions: env.CODEX_INTERCOM_BRIDGE_INSTRUCTIONS?.trim() || undefined,
|
|
230
|
+
...(bossClient === undefined ? {} : { bossClient }),
|
|
231
|
+
};
|
|
232
|
+
assertHardenedBossAgentConfig(agent);
|
|
233
|
+
return {
|
|
234
|
+
statePath: env.CODEX_INTERCOM_BRIDGE_STATE?.trim() || DEFAULT_BRIDGE_STATE_PATH,
|
|
235
|
+
agents: [agent],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function loadBridgeConfig(path = process.env.CODEX_INTERCOM_BRIDGE_CONFIG || DEFAULT_BRIDGE_CONFIG_PATH): BridgeConfig {
|
|
240
|
+
if (!existsSync(path)) return defaultBridgeConfig();
|
|
241
|
+
|
|
242
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
243
|
+
if (!isRecord(parsed)) throw new Error("Bridge config must be a JSON object");
|
|
244
|
+
if (!Array.isArray(parsed.agents)) throw new Error("Bridge config requires an agents array");
|
|
245
|
+
|
|
246
|
+
const appServer: BridgeConfig["appServer"] = isRecord(parsed.appServer) ? {
|
|
247
|
+
command: optionalString(parsed.appServer.command, "appServer.command"),
|
|
248
|
+
args: Array.isArray(parsed.appServer.args) ? parsed.appServer.args.map((arg, index) => requireString(arg, `appServer.args[${index}]`)) : undefined,
|
|
249
|
+
transport: parsed.appServer.transport === "unix-websocket" || parsed.appServer.transport === "stdio" ? parsed.appServer.transport : undefined,
|
|
250
|
+
socketPath: optionalString(parsed.appServer.socketPath, "appServer.socketPath"),
|
|
251
|
+
startDaemon: typeof parsed.appServer.startDaemon === "boolean" ? parsed.appServer.startDaemon : undefined,
|
|
252
|
+
startDaemonCommand: optionalString(parsed.appServer.startDaemonCommand, "appServer.startDaemonCommand"),
|
|
253
|
+
startDaemonArgs: Array.isArray(parsed.appServer.startDaemonArgs) ? parsed.appServer.startDaemonArgs.map((arg, index) => requireString(arg, `appServer.startDaemonArgs[${index}]`)) : undefined,
|
|
254
|
+
} : undefined;
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
statePath: resolve(optionalString(parsed.statePath, "statePath") ?? DEFAULT_BRIDGE_STATE_PATH),
|
|
258
|
+
agents: parsed.agents.map(normalizeAgent),
|
|
259
|
+
...(appServer ? { appServer } : {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function loadBridgeState(path: string): BridgeState {
|
|
264
|
+
if (!existsSync(path)) return { agents: {} };
|
|
265
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
266
|
+
if (!isRecord(parsed) || !isRecord(parsed.agents)) return { agents: {} };
|
|
267
|
+
const agents: BridgeState["agents"] = {};
|
|
268
|
+
for (const [id, value] of Object.entries(parsed.agents)) {
|
|
269
|
+
if (!isRecord(value) || typeof value.threadId !== "string") continue;
|
|
270
|
+
agents[id] = {
|
|
271
|
+
threadId: value.threadId,
|
|
272
|
+
updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : 0,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { agents };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function saveBridgeState(path: string, state: BridgeState): void {
|
|
279
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
280
|
+
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
281
|
+
restrictIntercomRuntimeFile(path);
|
|
282
|
+
}
|