@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.
Files changed (52) hide show
  1. package/.codex-plugin/plugin.json +39 -0
  2. package/.mcp.json +10 -0
  3. package/COPYRIGHT +6 -0
  4. package/LICENSE +661 -0
  5. package/LICENSE_TRANSITION.md +25 -0
  6. package/README.md +572 -0
  7. package/THIRD_PARTY_NOTICES.md +17 -0
  8. package/assets/logo-generated.png +0 -0
  9. package/assets/logo.svg +22 -0
  10. package/boss-control-outbox.ts +195 -0
  11. package/broker/access-credential.ts +76 -0
  12. package/broker/access-registry.ts +464 -0
  13. package/broker/audit.ts +60 -0
  14. package/broker/authorization.ts +54 -0
  15. package/broker/boss-adapter.ts +531 -0
  16. package/broker/boss-control-ledger.ts +306 -0
  17. package/broker/broker.ts +2327 -0
  18. package/broker/client.ts +1111 -0
  19. package/broker/framing.ts +87 -0
  20. package/broker/ownership.ts +61 -0
  21. package/broker/paths.ts +161 -0
  22. package/broker/spawn.ts +453 -0
  23. package/codex/app-server-client.ts +562 -0
  24. package/codex/boss-client.ts +34 -0
  25. package/codex/bridge-config.ts +282 -0
  26. package/codex/bridge-daemon.ts +913 -0
  27. package/codex/clipboard.ts +142 -0
  28. package/codex/coi.ts +802 -0
  29. package/codex/contact.ts +54 -0
  30. package/codex/mcp-protocol.ts +248 -0
  31. package/codex/runtime.ts +488 -0
  32. package/codex/server.ts +60 -0
  33. package/codex/team.ts +249 -0
  34. package/codex/tui-input.ts +174 -0
  35. package/config.ts +195 -0
  36. package/dist/bridge-daemon.mjs +3781 -0
  37. package/dist/broker.mjs +3012 -0
  38. package/dist/build-info.json +12 -0
  39. package/dist/codex-server.mjs +2890 -0
  40. package/dist/coi.mjs +4686 -0
  41. package/durable-json.ts +25 -0
  42. package/licenses/MIT-pi-intercom.txt +21 -0
  43. package/outbound-outbox.ts +116 -0
  44. package/package.json +89 -0
  45. package/protocol-v4/contract.ts +27 -0
  46. package/provider/protected-service.ts +162 -0
  47. package/provider/provider.mjs +34 -0
  48. package/scripts/build-info.mjs +58 -0
  49. package/scripts/build.mjs +55 -0
  50. package/skills/codex-intercom/SKILL.md +104 -0
  51. package/tsconfig.json +16 -0
  52. package/types.ts +225 -0
@@ -0,0 +1,195 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { canonicalHash } from "@ctliz/agent-intercom-core/canonical";
5
+ import { parseBossControlEnvelope, type BossControlEnvelope } from "@ctliz/agent-intercom-core/boss";
6
+ import { assertBossCanonicalData } from "./broker/boss-adapter.ts";
7
+ import { ensureIntercomRuntimeDir, getIntercomDirPath, INTERCOM_DIR_MODE, restrictIntercomRuntimeFile } from "./broker/paths.ts";
8
+ import { writeDurableJson } from "./durable-json.ts";
9
+
10
+ const BOSS_CONTROL_OUTBOX_VERSION = 2;
11
+ const MAX_BOSS_CONTROL_OUTBOX_ENTRIES = 256;
12
+
13
+ export interface StoredBossControl {
14
+ to: string;
15
+ envelope: BossControlEnvelope;
16
+ scope: string;
17
+ fingerprint: string;
18
+ queuedAt: number;
19
+ state: "queued" | "accepted";
20
+ deliveryId?: string;
21
+ }
22
+
23
+ interface BossControlOutboxState {
24
+ version: typeof BOSS_CONTROL_OUTBOX_VERSION;
25
+ entries: StoredBossControl[];
26
+ }
27
+
28
+ function scope(envelope: BossControlEnvelope): string {
29
+ return canonicalHash("agent-intercom-codex/boss-control/outbox-scope/v1", {
30
+ bossRunId: envelope.bossRunId,
31
+ participantId: envelope.participantId,
32
+ bindingEpoch: Number(envelope.bindingEpoch),
33
+ idempotencyKey: envelope.idempotencyKey,
34
+ });
35
+ }
36
+
37
+ function fingerprint(to: string, envelope: BossControlEnvelope): string {
38
+ const { messageId: _transportMessageId, ...stableEnvelope } = envelope;
39
+ return canonicalHash("agent-intercom-codex/boss-control/outbox-request/v1", { to, envelope: stableEnvelope });
40
+ }
41
+
42
+ function exactKeys(value: Record<string, unknown>, required: readonly string[], optional: readonly string[] = []): boolean {
43
+ const permitted = new Set([...required, ...optional]);
44
+ const keys = Reflect.ownKeys(value);
45
+ return required.every((key) => Object.hasOwn(value, key))
46
+ && keys.every((key) => typeof key === "string" && permitted.has(key));
47
+ }
48
+
49
+ function parseEntry(value: unknown): StoredBossControl {
50
+ assertBossCanonicalData(value, "$.bossControlOutbox.entries[]");
51
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Invalid Boss outbox entry");
52
+ const entry = value as Record<string, unknown>;
53
+ if (!exactKeys(entry, ["to", "envelope", "scope", "fingerprint", "queuedAt", "state"], ["deliveryId"])) {
54
+ throw new Error("Invalid Boss outbox entry fields");
55
+ }
56
+ if (
57
+ typeof entry.to !== "string"
58
+ || entry.to.length === 0
59
+ || typeof entry.scope !== "string"
60
+ || !/^[a-f0-9]{64}$/.test(entry.scope)
61
+ || typeof entry.fingerprint !== "string"
62
+ || !/^[a-f0-9]{64}$/.test(entry.fingerprint)
63
+ || typeof entry.queuedAt !== "number"
64
+ || !Number.isSafeInteger(entry.queuedAt)
65
+ || (entry.state !== "queued" && entry.state !== "accepted")
66
+ || (entry.state === "queued" && Object.hasOwn(entry, "deliveryId"))
67
+ || (entry.state === "accepted" && (!Object.hasOwn(entry, "deliveryId") || typeof entry.deliveryId !== "string" || entry.deliveryId.length === 0))
68
+ ) throw new Error("Invalid Boss outbox entry binding");
69
+ const envelope = parseBossControlEnvelope(entry.envelope);
70
+ if (entry.scope !== scope(envelope) || entry.fingerprint !== fingerprint(entry.to, envelope)) {
71
+ throw new Error("Boss outbox entry canonical binding mismatch");
72
+ }
73
+ return {
74
+ to: entry.to,
75
+ envelope,
76
+ scope: entry.scope,
77
+ fingerprint: entry.fingerprint,
78
+ queuedAt: entry.queuedAt,
79
+ state: entry.state,
80
+ ...(entry.deliveryId === undefined ? {} : { deliveryId: entry.deliveryId as string }),
81
+ };
82
+ }
83
+
84
+ function fileName(sessionId: string): string {
85
+ return `${createHash("sha256").update(sessionId).digest("hex")}.json`;
86
+ }
87
+
88
+ export class PersistentBossControlOutbox {
89
+ private readonly path: string;
90
+ private state: BossControlOutboxState;
91
+
92
+ constructor(sessionId: string, intercomDir: string = getIntercomDirPath()) {
93
+ ensureIntercomRuntimeDir(intercomDir);
94
+ const directory = join(intercomDir, "boss-control-outbox");
95
+ mkdirSync(directory, { recursive: true, mode: INTERCOM_DIR_MODE });
96
+ if (process.platform !== "win32") chmodSync(directory, INTERCOM_DIR_MODE);
97
+ this.path = join(directory, fileName(sessionId));
98
+ this.state = this.load();
99
+ }
100
+
101
+ list(): StoredBossControl[] {
102
+ return structuredClone(this.state.entries);
103
+ }
104
+
105
+ find(idempotencyKey: string): StoredBossControl | undefined {
106
+ const entry = this.state.entries.find((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
107
+ return entry === undefined ? undefined : structuredClone(entry);
108
+ }
109
+
110
+ enqueue(to: string, envelopeValue: unknown): "added" | "existing" {
111
+ if (typeof to !== "string" || to.length === 0) throw new Error("Boss target session ID is required");
112
+ assertBossCanonicalData(envelopeValue, "$.envelope");
113
+ const envelope = parseBossControlEnvelope(envelopeValue);
114
+ const candidateScope = scope(envelope);
115
+ const candidateFingerprint = fingerprint(to, envelope);
116
+ const existing = this.state.entries.find((entry) => entry.scope === candidateScope);
117
+ if (existing) {
118
+ if (existing.fingerprint !== candidateFingerprint) {
119
+ throw new Error(`Boss idempotency key ${envelope.idempotencyKey} is queued with a different canonical request`);
120
+ }
121
+ if (existing.envelope.messageId !== envelope.messageId) {
122
+ existing.envelope = envelope;
123
+ existing.queuedAt = Date.now();
124
+ this.persist();
125
+ }
126
+ return "existing";
127
+ }
128
+ if (this.state.entries.some((entry) => entry.envelope.messageId === envelope.messageId)) {
129
+ throw new Error(`Boss message ID ${envelope.messageId} is queued with a different idempotency scope`);
130
+ }
131
+ if (this.state.entries.length >= MAX_BOSS_CONTROL_OUTBOX_ENTRIES) throw new Error("Durable Boss control outbox is full");
132
+ this.state.entries.push({
133
+ to,
134
+ envelope,
135
+ scope: candidateScope,
136
+ fingerprint: candidateFingerprint,
137
+ queuedAt: Date.now(),
138
+ state: "queued",
139
+ });
140
+ this.persist();
141
+ return "added";
142
+ }
143
+
144
+ markAccepted(idempotencyKey: string, messageId: string, deliveryId: string): "accepted" | "already-accepted" {
145
+ const entry = this.state.entries.find((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
146
+ if (!entry || entry.envelope.messageId !== messageId || !deliveryId) {
147
+ throw new Error("Boss acknowledgement does not match the durable outbox binding");
148
+ }
149
+ if (entry.state === "accepted") {
150
+ if (entry.deliveryId !== deliveryId) throw new Error("Boss acknowledgement changed the durable deliveryId");
151
+ return "already-accepted";
152
+ }
153
+ entry.state = "accepted";
154
+ entry.deliveryId = deliveryId;
155
+ this.persist();
156
+ return "accepted";
157
+ }
158
+
159
+ removeCorrelated(idempotencyKey: string, messageId: string, deliveryId?: string): void {
160
+ const index = this.state.entries.findIndex((candidate) => candidate.envelope.idempotencyKey === idempotencyKey);
161
+ if (index < 0) throw new Error("Boss terminal result has no durable outbox binding");
162
+ const entry = this.state.entries[index];
163
+ if (entry.envelope.messageId !== messageId) throw new Error("Boss terminal result messageId does not match the durable caller");
164
+ if (deliveryId === undefined) {
165
+ if (entry.state !== "queued") throw new Error("Boss post-acceptance failure omitted the durable deliveryId");
166
+ } else if (entry.state !== "accepted" || entry.deliveryId !== deliveryId) {
167
+ throw new Error("Boss terminal result arrived before the matching durable acknowledgement");
168
+ }
169
+ this.state.entries.splice(index, 1);
170
+ this.persist();
171
+ }
172
+
173
+ private load(): BossControlOutboxState {
174
+ if (!existsSync(this.path)) return { version: BOSS_CONTROL_OUTBOX_VERSION, entries: [] };
175
+ try {
176
+ const parsed: unknown = JSON.parse(readFileSync(this.path, "utf8"));
177
+ assertBossCanonicalData(parsed, "$.bossControlOutbox");
178
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
179
+ const state = parsed as Record<string, unknown>;
180
+ if (!exactKeys(state, ["version", "entries"]) || state.version !== BOSS_CONTROL_OUTBOX_VERSION || !Array.isArray(state.entries)) {
181
+ throw new Error("invalid Boss outbox state");
182
+ }
183
+ return { version: BOSS_CONTROL_OUTBOX_VERSION, entries: state.entries.map(parseEntry) };
184
+ } catch (error) {
185
+ const corruptPath = `${this.path}.corrupt-${Date.now()}`;
186
+ renameSync(this.path, corruptPath);
187
+ restrictIntercomRuntimeFile(corruptPath);
188
+ throw new Error(`Boss control outbox was corrupt and quarantined at ${corruptPath}`, { cause: error });
189
+ }
190
+ }
191
+
192
+ private persist(): void {
193
+ writeDurableJson(this.path, this.state);
194
+ }
195
+ }
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from "fs";
2
+ import { writeDurableJson } from "../durable-json.ts";
3
+ import type { RemoteAccessMetadata, RemoteRegistrationAccess } from "../types.ts";
4
+
5
+ export const ACCESS_CREDENTIAL_ENV = "AGENT_INTERCOM_ACCESS_CREDENTIAL_PATH";
6
+ export const ACCESS_CREDENTIAL_VERSION = 1;
7
+
8
+ export interface EnrollmentCredentialFile {
9
+ version?: typeof ACCESS_CREDENTIAL_VERSION;
10
+ enrollmentToken: string;
11
+ }
12
+
13
+ export interface SessionCredentialFile {
14
+ version: typeof ACCESS_CREDENTIAL_VERSION;
15
+ sessionCredential: string;
16
+ sessionId: string;
17
+ generation: number;
18
+ }
19
+
20
+ export interface LoadedRemoteAccessCredential {
21
+ path: string;
22
+ access: RemoteRegistrationAccess;
23
+ enrollment: boolean;
24
+ }
25
+
26
+ function nonEmptyString(value: unknown): value is string {
27
+ return typeof value === "string" && value.length > 0 && !value.includes("\0");
28
+ }
29
+
30
+ export function loadRemoteAccessCredential(env: NodeJS.ProcessEnv = process.env): LoadedRemoteAccessCredential | undefined {
31
+ const path = env[ACCESS_CREDENTIAL_ENV]?.trim();
32
+ if (!path) return undefined;
33
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
34
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
35
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
36
+ }
37
+ const credential = parsed as Record<string, unknown>;
38
+ if (nonEmptyString(credential.enrollmentToken)) {
39
+ return { path, access: { enrollmentToken: credential.enrollmentToken }, enrollment: true };
40
+ }
41
+ if (
42
+ credential.version === ACCESS_CREDENTIAL_VERSION
43
+ && nonEmptyString(credential.sessionCredential)
44
+ && nonEmptyString(credential.sessionId)
45
+ && typeof credential.generation === "number"
46
+ && Number.isSafeInteger(credential.generation)
47
+ && credential.generation > 0
48
+ ) {
49
+ return {
50
+ path,
51
+ access: {
52
+ sessionCredential: credential.sessionCredential,
53
+ sessionId: credential.sessionId,
54
+ generation: credential.generation,
55
+ },
56
+ enrollment: false,
57
+ };
58
+ }
59
+ throw new Error(`Invalid Agent Intercom access credential at ${path}`);
60
+ }
61
+
62
+ export function writeRemoteSessionCredential(
63
+ path: string,
64
+ sessionId: string,
65
+ metadata: RemoteAccessMetadata,
66
+ ): void {
67
+ if (!metadata.sessionCredential) {
68
+ throw new Error("Remote enrollment response omitted the session credential");
69
+ }
70
+ writeDurableJson(path, {
71
+ version: ACCESS_CREDENTIAL_VERSION,
72
+ sessionCredential: metadata.sessionCredential,
73
+ sessionId,
74
+ generation: metadata.generation,
75
+ } satisfies SessionCredentialFile);
76
+ }