@ctliz/agent-intercom-claude 0.12.0-connect.3

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 (59) hide show
  1. package/.claude-plugin/plugin.json +26 -0
  2. package/.mcp.json +10 -0
  3. package/CHANGELOG.md +62 -0
  4. package/COPYRIGHT +6 -0
  5. package/LICENSE +661 -0
  6. package/LICENSE_TRANSITION.md +25 -0
  7. package/README.md +506 -0
  8. package/THIRD_PARTY_NOTICES.md +25 -0
  9. package/broker/access-credential.ts +76 -0
  10. package/broker/access-registry.ts +460 -0
  11. package/broker/audit.ts +60 -0
  12. package/broker/authorization.ts +125 -0
  13. package/broker/boss-contracts.ts +267 -0
  14. package/broker/broker.ts +2028 -0
  15. package/broker/client.ts +1048 -0
  16. package/broker/framing.ts +87 -0
  17. package/broker/ownership.ts +61 -0
  18. package/broker/paths.ts +161 -0
  19. package/broker/spawn.ts +463 -0
  20. package/broker/validation.ts +132 -0
  21. package/claude/cci.ts +512 -0
  22. package/claude/ccim.ts +31 -0
  23. package/claude/cli-runner.ts +220 -0
  24. package/claude/contact.ts +33 -0
  25. package/claude/inbox-monitor.ts +49 -0
  26. package/claude/inbox.ts +77 -0
  27. package/claude/mcp-protocol.ts +253 -0
  28. package/claude/native-bridge.ts +219 -0
  29. package/claude/native-protocol.ts +346 -0
  30. package/claude/permission-policy.ts +287 -0
  31. package/claude/runtime.ts +510 -0
  32. package/claude/server.ts +69 -0
  33. package/claude/team.ts +348 -0
  34. package/claude/transport.ts +97 -0
  35. package/claude/worker-config.ts +198 -0
  36. package/claude/worker-daemon.ts +418 -0
  37. package/commands/intercom-id.md +9 -0
  38. package/commands/intercom.md +8 -0
  39. package/config.ts +195 -0
  40. package/dist/broker.mjs +2530 -0
  41. package/dist/cci.mjs +3607 -0
  42. package/dist/ccim.mjs +3621 -0
  43. package/dist/claude-server.mjs +2730 -0
  44. package/dist/inbox-monitor.mjs +68 -0
  45. package/dist/worker-daemon.mjs +2797 -0
  46. package/durable-json.ts +25 -0
  47. package/licenses/MIT-pi-claude-link.txt +21 -0
  48. package/licenses/MIT-pi-intercom.txt +21 -0
  49. package/monitors/monitors.json +8 -0
  50. package/outbound-outbox.ts +116 -0
  51. package/package.json +93 -0
  52. package/protocol-v4/contract.ts +45 -0
  53. package/provider/protected-service.ts +162 -0
  54. package/provider/provider.mjs +34 -0
  55. package/scripts/build.mjs +71 -0
  56. package/scripts/verify-core-provenance.mjs +64 -0
  57. package/scripts/verify-packed-runtime.mjs +140 -0
  58. package/skills/claude-intercom/SKILL.md +90 -0
  59. package/types.ts +212 -0
@@ -0,0 +1,87 @@
1
+ import type { Socket } from "net";
2
+
3
+ export const MAX_FRAME_BYTES = 1024 * 1024;
4
+
5
+ /**
6
+ * Write a length-prefixed message to a socket.
7
+ * Format: 4-byte big-endian length + JSON payload
8
+ */
9
+ export function writeMessage(socket: Socket, msg: unknown): void {
10
+ const json = JSON.stringify(msg);
11
+ const payload = Buffer.from(json, "utf-8");
12
+ const header = Buffer.alloc(4);
13
+ header.writeUInt32BE(payload.length, 0);
14
+ socket.write(Buffer.concat([header, payload]));
15
+ }
16
+
17
+ /**
18
+ * Create a message reader that handles partial reads.
19
+ * Calls onMessage for each complete message received.
20
+ * Protocol or handler errors are reported to onError so the caller can close the socket.
21
+ */
22
+ export function createMessageReader(
23
+ onMessage: (msg: unknown) => void,
24
+ onError: (error: Error) => void,
25
+ maxFrameBytes = MAX_FRAME_BYTES,
26
+ ) {
27
+ let buffer = Buffer.alloc(0);
28
+
29
+ function reportMessage(payload: Buffer): boolean {
30
+ let msg: unknown;
31
+ try {
32
+ msg = JSON.parse(payload.toString("utf-8"));
33
+ } catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error }));
36
+ return false;
37
+ }
38
+
39
+ try {
40
+ onMessage(msg);
41
+ return true;
42
+ } catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ onError(new Error(`Failed to handle intercom message: ${message}`, { cause: error }));
45
+ return false;
46
+ }
47
+ }
48
+
49
+ return (data: Buffer) => {
50
+ let remaining = data;
51
+
52
+ while (remaining.length > 0) {
53
+ if (buffer.length < 4) {
54
+ const headerBytes = Math.min(4 - buffer.length, remaining.length);
55
+ buffer = Buffer.concat([buffer, remaining.subarray(0, headerBytes)]);
56
+ remaining = remaining.subarray(headerBytes);
57
+ if (buffer.length < 4) {
58
+ return;
59
+ }
60
+ }
61
+
62
+ const length = buffer.readUInt32BE(0);
63
+ if (length > maxFrameBytes) {
64
+ buffer = Buffer.alloc(0);
65
+ onError(new Error(`Intercom frame length ${length} exceeds maximum ${maxFrameBytes} bytes`));
66
+ return;
67
+ }
68
+
69
+ const missingPayloadBytes = length - Math.max(0, buffer.length - 4);
70
+ const payloadBytes = Math.min(missingPayloadBytes, remaining.length);
71
+ if (payloadBytes > 0) {
72
+ buffer = Buffer.concat([buffer, remaining.subarray(0, payloadBytes)]);
73
+ remaining = remaining.subarray(payloadBytes);
74
+ }
75
+
76
+ if (buffer.length < 4 + length) {
77
+ return;
78
+ }
79
+
80
+ const payload = buffer.subarray(4, 4 + length);
81
+ buffer = Buffer.alloc(0);
82
+ if (!reportMessage(payload)) {
83
+ return;
84
+ }
85
+ }
86
+ };
87
+ }
@@ -0,0 +1,61 @@
1
+ import { closeSync, constants, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { INTERCOM_RUNTIME_FILE_MODE, restrictIntercomRuntimeFile } from "./paths.ts";
3
+
4
+ function ownerPid(path: string): number | null {
5
+ try {
6
+ const pid = Number.parseInt(readFileSync(path, "utf8").trim(), 10);
7
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ function pidIsAlive(pid: number): boolean {
14
+ try {
15
+ process.kill(pid, 0);
16
+ return true;
17
+ } catch (error) {
18
+ return (error as NodeJS.ErrnoException).code !== "ESRCH";
19
+ }
20
+ }
21
+
22
+ export function acquireBrokerOwnership(path: string, pid = process.pid): void {
23
+ for (let attempt = 0; attempt < 2; attempt += 1) {
24
+ try {
25
+ const fd = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, INTERCOM_RUNTIME_FILE_MODE);
26
+ try {
27
+ writeFileSync(fd, String(pid));
28
+ } finally {
29
+ closeSync(fd);
30
+ }
31
+ restrictIntercomRuntimeFile(path);
32
+ return;
33
+ } catch (error) {
34
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
35
+ const existingPid = ownerPid(path);
36
+ if (existingPid !== null && pidIsAlive(existingPid)) {
37
+ throw new Error(`Intercom broker already owned by live process ${existingPid}`);
38
+ }
39
+ try {
40
+ unlinkSync(path);
41
+ } catch (unlinkError) {
42
+ if ((unlinkError as NodeJS.ErrnoException).code !== "ENOENT") throw unlinkError;
43
+ }
44
+ }
45
+ }
46
+ throw new Error("Could not acquire intercom broker ownership");
47
+ }
48
+
49
+ export function releaseBrokerOwnership(path: string, pid = process.pid): void {
50
+ if (ownerPid(path) !== pid) return;
51
+ try {
52
+ unlinkSync(path);
53
+ } catch (error) {
54
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
55
+ }
56
+ }
57
+
58
+ export function hasBrokerOwnership(path: string, pid = process.pid): boolean {
59
+ return ownerPid(path) === pid;
60
+ }
61
+
@@ -0,0 +1,161 @@
1
+ import { chmodSync, mkdirSync, readFileSync } from "fs";
2
+ import { isAbsolute, join, resolve } from "path";
3
+ import { homedir } from "os";
4
+
5
+ export const INTERCOM_DIR_MODE = 0o700;
6
+ export const INTERCOM_RUNTIME_FILE_MODE = 0o600;
7
+ export const INTERCOM_TCP_HOST = "127.0.0.1";
8
+ export const INTERCOM_PROTOCOL_NAME = "pi-intercom";
9
+ export const INTERCOM_PROTOCOL_VERSION = 4;
10
+
11
+ export interface BrokerTcpEndpoint {
12
+ transport: "tcp";
13
+ host: string;
14
+ port: number;
15
+ stateId?: string;
16
+ }
17
+
18
+ export type BrokerConnectTarget = string | BrokerTcpEndpoint;
19
+
20
+ function sanitizePipeSegment(value: string): string {
21
+ return value
22
+ .replace(/[^a-zA-Z0-9]+/g, "-")
23
+ .replace(/^-+|-+$/g, "")
24
+ .toLowerCase() || "default";
25
+ }
26
+
27
+ export function getAgentDirPath(
28
+ env: NodeJS.ProcessEnv = process.env,
29
+ homeDir: string = homedir(),
30
+ cwd: string = process.cwd(),
31
+ ): string {
32
+ const configured = env.PI_CODING_AGENT_DIR?.trim();
33
+ if (!configured) {
34
+ return join(homeDir, ".pi/agent");
35
+ }
36
+
37
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
38
+ }
39
+
40
+ export function getIntercomDirPath(agentDir: string = getAgentDirPath()): string {
41
+ return join(agentDir, "intercom");
42
+ }
43
+
44
+ export function shouldUseWindowsTcpTransport(
45
+ platform: NodeJS.Platform = process.platform,
46
+ env: NodeJS.ProcessEnv = process.env,
47
+ ): boolean {
48
+ if (platform !== "win32") {
49
+ return false;
50
+ }
51
+
52
+ const transport = env.PI_INTERCOM_TRANSPORT?.trim().toLowerCase();
53
+ if (transport === "tcp") {
54
+ return true;
55
+ }
56
+
57
+ const legacyOptIn = env.PI_INTERCOM_TCP?.trim().toLowerCase();
58
+ return legacyOptIn === "1" || legacyOptIn === "true";
59
+ }
60
+
61
+ export function getBrokerPortFilePath(intercomDir: string = getIntercomDirPath()): string {
62
+ return join(intercomDir, "broker.port.json");
63
+ }
64
+
65
+ export function getBrokerAskStateFilePath(intercomDir: string = getIntercomDirPath()): string {
66
+ return join(intercomDir, "broker-asks.json");
67
+ }
68
+
69
+ export function getBrokerAccessStateFilePath(intercomDir: string = getIntercomDirPath()): string {
70
+ return join(intercomDir, "broker-access.json");
71
+ }
72
+
73
+ export function getBrokerAdminCredentialFilePath(intercomDir: string = getIntercomDirPath()): string {
74
+ return join(intercomDir, "broker-admin.json");
75
+ }
76
+
77
+ export function getBrokerAuditFilePath(intercomDir: string = getIntercomDirPath()): string {
78
+ return join(intercomDir, "broker-audit.jsonl");
79
+ }
80
+
81
+ export function getRemoteGatewaySocketPath(
82
+ platform: NodeJS.Platform = process.platform,
83
+ agentDir: string = getAgentDirPath(),
84
+ ): string {
85
+ if (platform === "win32") {
86
+ return `\\\\.\\pipe\\pi-intercom-remote-${sanitizePipeSegment(agentDir)}`;
87
+ }
88
+ return join(getIntercomDirPath(agentDir), "remote-gateway.sock");
89
+ }
90
+
91
+ export function getBrokerSocketPath(
92
+ platform: NodeJS.Platform = process.platform,
93
+ agentDir: string = getAgentDirPath(),
94
+ ): string {
95
+ if (platform === "win32") {
96
+ return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(agentDir)}`;
97
+ }
98
+
99
+ return join(getIntercomDirPath(agentDir), "broker.sock");
100
+ }
101
+
102
+ export function getBrokerConnectTarget(
103
+ platform: NodeJS.Platform = process.platform,
104
+ env: NodeJS.ProcessEnv = process.env,
105
+ intercomDir: string = getIntercomDirPath(getAgentDirPath(env)),
106
+ ): BrokerConnectTarget {
107
+ if (shouldUseWindowsTcpTransport(platform, env)) {
108
+ const endpointFile = getBrokerPortFilePath(intercomDir);
109
+ const raw = readFileSync(endpointFile, "utf-8");
110
+ const parsed: unknown = JSON.parse(raw);
111
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
112
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}: expected a JSON object`);
113
+ }
114
+ const endpoint = parsed as Record<string, unknown>;
115
+ if (
116
+ endpoint.transport !== "tcp"
117
+ || endpoint.host !== INTERCOM_TCP_HOST
118
+ || typeof endpoint.port !== "number"
119
+ || !Number.isSafeInteger(endpoint.port)
120
+ || endpoint.port <= 0
121
+ || endpoint.port > 65535
122
+ || typeof endpoint.stateId !== "string"
123
+ || endpoint.stateId.length === 0
124
+ ) {
125
+ throw new Error(`Invalid intercom TCP endpoint at ${endpointFile}`);
126
+ }
127
+ return { transport: "tcp", host: endpoint.host, port: endpoint.port, stateId: endpoint.stateId };
128
+ }
129
+
130
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
131
+ }
132
+
133
+ export function getBrokerListenTarget(
134
+ platform: NodeJS.Platform = process.platform,
135
+ env: NodeJS.ProcessEnv = process.env,
136
+ ): BrokerConnectTarget {
137
+ if (shouldUseWindowsTcpTransport(platform, env)) {
138
+ return { transport: "tcp", host: INTERCOM_TCP_HOST, port: 0 };
139
+ }
140
+
141
+ return getBrokerSocketPath(platform, getAgentDirPath(env));
142
+ }
143
+
144
+ export function ensureIntercomRuntimeDir(
145
+ intercomDir: string = getIntercomDirPath(),
146
+ platform: NodeJS.Platform = process.platform,
147
+ ): void {
148
+ mkdirSync(intercomDir, { recursive: true, mode: INTERCOM_DIR_MODE });
149
+ if (platform !== "win32") {
150
+ chmodSync(intercomDir, INTERCOM_DIR_MODE);
151
+ }
152
+ }
153
+
154
+ export function restrictIntercomRuntimeFile(
155
+ filePath: string,
156
+ platform: NodeJS.Platform = process.platform,
157
+ ): void {
158
+ if (platform !== "win32") {
159
+ chmodSync(filePath, INTERCOM_RUNTIME_FILE_MODE);
160
+ }
161
+ }