@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.
- package/.claude-plugin/plugin.json +26 -0
- package/.mcp.json +10 -0
- package/CHANGELOG.md +62 -0
- package/COPYRIGHT +6 -0
- package/LICENSE +661 -0
- package/LICENSE_TRANSITION.md +25 -0
- package/README.md +506 -0
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/broker/access-credential.ts +76 -0
- package/broker/access-registry.ts +460 -0
- package/broker/audit.ts +60 -0
- package/broker/authorization.ts +125 -0
- package/broker/boss-contracts.ts +267 -0
- package/broker/broker.ts +2028 -0
- package/broker/client.ts +1048 -0
- package/broker/framing.ts +87 -0
- package/broker/ownership.ts +61 -0
- package/broker/paths.ts +161 -0
- package/broker/spawn.ts +463 -0
- package/broker/validation.ts +132 -0
- package/claude/cci.ts +512 -0
- package/claude/ccim.ts +31 -0
- package/claude/cli-runner.ts +220 -0
- package/claude/contact.ts +33 -0
- package/claude/inbox-monitor.ts +49 -0
- package/claude/inbox.ts +77 -0
- package/claude/mcp-protocol.ts +253 -0
- package/claude/native-bridge.ts +219 -0
- package/claude/native-protocol.ts +346 -0
- package/claude/permission-policy.ts +287 -0
- package/claude/runtime.ts +510 -0
- package/claude/server.ts +69 -0
- package/claude/team.ts +348 -0
- package/claude/transport.ts +97 -0
- package/claude/worker-config.ts +198 -0
- package/claude/worker-daemon.ts +418 -0
- package/commands/intercom-id.md +9 -0
- package/commands/intercom.md +8 -0
- package/config.ts +195 -0
- package/dist/broker.mjs +2530 -0
- package/dist/cci.mjs +3607 -0
- package/dist/ccim.mjs +3621 -0
- package/dist/claude-server.mjs +2730 -0
- package/dist/inbox-monitor.mjs +68 -0
- package/dist/worker-daemon.mjs +2797 -0
- package/durable-json.ts +25 -0
- package/licenses/MIT-pi-claude-link.txt +21 -0
- package/licenses/MIT-pi-intercom.txt +21 -0
- package/monitors/monitors.json +8 -0
- package/outbound-outbox.ts +116 -0
- package/package.json +93 -0
- package/protocol-v4/contract.ts +45 -0
- package/provider/protected-service.ts +162 -0
- package/provider/provider.mjs +34 -0
- package/scripts/build.mjs +71 -0
- package/scripts/verify-core-provenance.mjs +64 -0
- package/scripts/verify-packed-runtime.mjs +140 -0
- package/skills/claude-intercom/SKILL.md +90 -0
- package/types.ts +212 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
constants,
|
|
4
|
+
fstatSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
openSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
type BigIntStats,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { dirname, isAbsolute, join, parse, relative, sep } from "node:path";
|
|
11
|
+
|
|
12
|
+
export const CLAUDE_PERMISSION_MODES = [
|
|
13
|
+
"acceptEdits",
|
|
14
|
+
"auto",
|
|
15
|
+
"bypassPermissions",
|
|
16
|
+
"manual",
|
|
17
|
+
"dontAsk",
|
|
18
|
+
"plan",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type ClaudePermissionMode = (typeof CLAUDE_PERMISSION_MODES)[number];
|
|
22
|
+
export type ClaudePermissionCeiling = "standard" | "read-only";
|
|
23
|
+
|
|
24
|
+
export interface ClaudePermissionPolicyInput {
|
|
25
|
+
permissionMode?: unknown;
|
|
26
|
+
dangerouslySkipPermissions?: unknown;
|
|
27
|
+
ceiling?: ClaudePermissionCeiling;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ClaudePermissionPolicy {
|
|
31
|
+
permissionMode?: ClaudePermissionMode;
|
|
32
|
+
dangerouslySkipPermissions: boolean;
|
|
33
|
+
ceiling: ClaudePermissionCeiling;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateClaudePermissionMode(value: unknown, field = "permissionMode"): ClaudePermissionMode {
|
|
37
|
+
if (typeof value !== "string" || !(CLAUDE_PERMISSION_MODES as readonly string[]).includes(value)) {
|
|
38
|
+
throw new Error(`${field} must be one of: ${CLAUDE_PERMISSION_MODES.join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
return value as ClaudePermissionMode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveClaudePermissionPolicy(input: ClaudePermissionPolicyInput): ClaudePermissionPolicy {
|
|
44
|
+
const ceiling = input.ceiling ?? "standard";
|
|
45
|
+
if (ceiling !== "standard" && ceiling !== "read-only") {
|
|
46
|
+
throw new Error("permission ceiling must be standard or read-only");
|
|
47
|
+
}
|
|
48
|
+
if (input.dangerouslySkipPermissions !== undefined && typeof input.dangerouslySkipPermissions !== "boolean") {
|
|
49
|
+
throw new Error("dangerouslySkipPermissions must be a boolean");
|
|
50
|
+
}
|
|
51
|
+
const dangerouslySkipPermissions = input.dangerouslySkipPermissions === true;
|
|
52
|
+
const requestedMode = input.permissionMode === undefined
|
|
53
|
+
? undefined
|
|
54
|
+
: validateClaudePermissionMode(input.permissionMode);
|
|
55
|
+
|
|
56
|
+
if (ceiling === "read-only") {
|
|
57
|
+
if (dangerouslySkipPermissions) {
|
|
58
|
+
throw new Error("read-only Claude roles cannot skip permission checks");
|
|
59
|
+
}
|
|
60
|
+
if (requestedMode !== undefined && requestedMode !== "plan") {
|
|
61
|
+
throw new Error("read-only Claude roles require permission mode plan");
|
|
62
|
+
}
|
|
63
|
+
// Claude applies the top-level plan-mode permission boundary to Task
|
|
64
|
+
// subagents as well, so an inner agent cannot exceed this ceiling.
|
|
65
|
+
return { permissionMode: "plan", dangerouslySkipPermissions: false, ceiling };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (dangerouslySkipPermissions) {
|
|
69
|
+
return { dangerouslySkipPermissions: true, ceiling };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
permissionMode: requestedMode ?? "manual",
|
|
73
|
+
dangerouslySkipPermissions: false,
|
|
74
|
+
ceiling,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const PERMISSION_OVERRIDE_OPTIONS = new Set([
|
|
79
|
+
"--allow-dangerously-skip-permissions",
|
|
80
|
+
"--dangerously-skip-permissions",
|
|
81
|
+
"--permission-mode",
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// These options can load or grant permissions outside the validated top-level
|
|
85
|
+
// mode. They remain ordinary Claude CLI features, but a hardened launch must
|
|
86
|
+
// not be able to re-introduce them through appended argv.
|
|
87
|
+
export const CLAUDE_2_1_220_HARDENED_CAPABILITY_OPTIONS = [
|
|
88
|
+
"--add-dir",
|
|
89
|
+
"--agent",
|
|
90
|
+
"--agents",
|
|
91
|
+
"--allowed-tools",
|
|
92
|
+
"--allowedTools",
|
|
93
|
+
"--mcp-config",
|
|
94
|
+
"--plugin-dir",
|
|
95
|
+
"--plugin-dir-no-mcp",
|
|
96
|
+
"--plugin-url",
|
|
97
|
+
"--setting-sources",
|
|
98
|
+
"--settings",
|
|
99
|
+
"--tools",
|
|
100
|
+
"--worktree",
|
|
101
|
+
"-w",
|
|
102
|
+
] as const;
|
|
103
|
+
|
|
104
|
+
const HARDENED_PERMISSION_CAPABILITY_OPTIONS = new Set<string>(
|
|
105
|
+
CLAUDE_2_1_220_HARDENED_CAPABILITY_OPTIONS,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
function permissionCapabilityOption(arg: string): string | undefined {
|
|
109
|
+
const equalsIndex = arg.indexOf("=");
|
|
110
|
+
const option = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
|
|
111
|
+
if (HARDENED_PERMISSION_CAPABILITY_OPTIONS.has(option)) return option;
|
|
112
|
+
// Commander can interpret a single-dash token as a short option, an
|
|
113
|
+
// attached value, or a cluster. Looking only at the first short option is
|
|
114
|
+
// unsafe: for example, `-pwoutside` can still contain the root-creating
|
|
115
|
+
// `-w` alias. Hardened roles therefore reject every appended single-dash
|
|
116
|
+
// token. Standard roles retain the ordinary Claude CLI behavior.
|
|
117
|
+
if (arg.length > 1 && arg.startsWith("-") && !arg.startsWith("--")) return arg;
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isContainedPath(root: string, candidate: string): boolean {
|
|
122
|
+
const pathFromRoot = relative(root, candidate);
|
|
123
|
+
return pathFromRoot === ""
|
|
124
|
+
|| (!isAbsolute(pathFromRoot) && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface InspectedDirectory {
|
|
128
|
+
canonicalPath: string;
|
|
129
|
+
descriptor: BigIntStats;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface HardenedClaudePathBinding {
|
|
133
|
+
cwd: string;
|
|
134
|
+
addDirs: string[];
|
|
135
|
+
release(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sameFile(left: BigIntStats, right: BigIntStats): boolean {
|
|
139
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pathError(field: string, detail: string, cause?: unknown): Error {
|
|
143
|
+
return cause === undefined
|
|
144
|
+
? new Error(`${field} ${detail}`)
|
|
145
|
+
: new Error(`${field} ${detail}`, { cause });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a path without allowing lexical normalization to hide a symlink.
|
|
150
|
+
* Each component is inspected before a later `..` component can remove it.
|
|
151
|
+
*/
|
|
152
|
+
function inspectExistingRealDirectory(input: string, base: string, field: string): InspectedDirectory {
|
|
153
|
+
if (input.length === 0) {
|
|
154
|
+
throw pathError(field, "must name an existing real directory");
|
|
155
|
+
}
|
|
156
|
+
const rawAbsolute = isAbsolute(input)
|
|
157
|
+
? input
|
|
158
|
+
: `${base.endsWith(sep) ? base : `${base}${sep}`}${input}`;
|
|
159
|
+
const root = parse(rawAbsolute).root;
|
|
160
|
+
let cursor = root;
|
|
161
|
+
|
|
162
|
+
const inspect = (path: string): BigIntStats => {
|
|
163
|
+
let descriptor: BigIntStats;
|
|
164
|
+
try {
|
|
165
|
+
descriptor = lstatSync(path, { bigint: true });
|
|
166
|
+
} catch (cause) {
|
|
167
|
+
throw pathError(field, "must name an existing real directory", cause);
|
|
168
|
+
}
|
|
169
|
+
if (descriptor.isSymbolicLink()) {
|
|
170
|
+
throw pathError(field, "must not contain a symbolic-link component");
|
|
171
|
+
}
|
|
172
|
+
if (!descriptor.isDirectory()) {
|
|
173
|
+
throw pathError(field, "must contain only existing directories");
|
|
174
|
+
}
|
|
175
|
+
return descriptor;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
let descriptor = inspect(cursor);
|
|
179
|
+
for (const component of rawAbsolute.slice(root.length).split(sep)) {
|
|
180
|
+
if (component === "" || component === ".") continue;
|
|
181
|
+
cursor = component === ".." ? dirname(cursor) : join(cursor, component);
|
|
182
|
+
descriptor = inspect(cursor);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let canonicalPath: string;
|
|
186
|
+
try {
|
|
187
|
+
canonicalPath = realpathSync(cursor);
|
|
188
|
+
} catch (cause) {
|
|
189
|
+
throw pathError(field, "cannot be bound to a canonical real directory", cause);
|
|
190
|
+
}
|
|
191
|
+
return { canonicalPath, descriptor };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function openBoundDirectory(input: string, base: string, field: string): { path: string; fd: number } {
|
|
195
|
+
if (typeof constants.O_DIRECTORY !== "number" || typeof constants.O_NOFOLLOW !== "number") {
|
|
196
|
+
throw pathError(field, "cannot be securely bound on this platform");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const first = inspectExistingRealDirectory(input, base, field);
|
|
200
|
+
let fd: number;
|
|
201
|
+
try {
|
|
202
|
+
fd = openSync(first.canonicalPath, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
203
|
+
} catch (cause) {
|
|
204
|
+
throw pathError(field, "cannot be bound to an open real directory", cause);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const opened = fstatSync(fd, { bigint: true });
|
|
209
|
+
if (!opened.isDirectory() || !sameFile(opened, first.descriptor)) {
|
|
210
|
+
throw pathError(field, "changed while its real directory was being bound");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Re-walk the caller's spelling after opening the directory. This catches
|
|
214
|
+
// a best-effort concurrent rename/symlink replacement and ensures only the
|
|
215
|
+
// stable canonical spelling is ever returned to a launcher.
|
|
216
|
+
const second = inspectExistingRealDirectory(input, base, field);
|
|
217
|
+
if (second.canonicalPath !== first.canonicalPath || !sameFile(opened, second.descriptor)) {
|
|
218
|
+
throw pathError(field, "changed while its real directory was being bound");
|
|
219
|
+
}
|
|
220
|
+
return { path: first.canonicalPath, fd };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
closeSync(fd);
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function bindHardenedClaudePaths(
|
|
228
|
+
cwd: string,
|
|
229
|
+
addDirs: readonly string[],
|
|
230
|
+
field = "hardened Claude paths",
|
|
231
|
+
): HardenedClaudePathBinding {
|
|
232
|
+
const descriptors: number[] = [];
|
|
233
|
+
try {
|
|
234
|
+
const cwdBinding = openBoundDirectory(cwd, process.cwd(), `${field}.cwd`);
|
|
235
|
+
descriptors.push(cwdBinding.fd);
|
|
236
|
+
|
|
237
|
+
const canonicalAddDirs = addDirs.map((addDir, index) => {
|
|
238
|
+
const binding = openBoundDirectory(addDir, cwdBinding.path, `${field}.addDirs[${index}]`);
|
|
239
|
+
descriptors.push(binding.fd);
|
|
240
|
+
if (!isContainedPath(cwdBinding.path, binding.path)) {
|
|
241
|
+
throw pathError(`${field}.addDirs[${index}]`, "must stay within the assigned workspace root");
|
|
242
|
+
}
|
|
243
|
+
return binding.path;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
let released = false;
|
|
247
|
+
return {
|
|
248
|
+
cwd: cwdBinding.path,
|
|
249
|
+
addDirs: canonicalAddDirs,
|
|
250
|
+
release() {
|
|
251
|
+
if (released) return;
|
|
252
|
+
released = true;
|
|
253
|
+
for (const fd of descriptors.splice(0)) closeSync(fd);
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
} catch (error) {
|
|
257
|
+
for (const fd of descriptors) closeSync(fd);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function assertAddDirsWithinRoot(
|
|
263
|
+
addDirs: readonly string[],
|
|
264
|
+
root: string,
|
|
265
|
+
field = "addDirs",
|
|
266
|
+
): void {
|
|
267
|
+
const binding = bindHardenedClaudePaths(root, addDirs, field);
|
|
268
|
+
binding.release();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function assertNoPermissionOverrides(
|
|
272
|
+
args: readonly string[],
|
|
273
|
+
field = "claudeArgs",
|
|
274
|
+
ceiling: ClaudePermissionCeiling = "standard",
|
|
275
|
+
): void {
|
|
276
|
+
if (ceiling !== "read-only") return;
|
|
277
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
278
|
+
const arg = args[index]!;
|
|
279
|
+
const option = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
280
|
+
if (PERMISSION_OVERRIDE_OPTIONS.has(option)) {
|
|
281
|
+
throw new Error(`${field}[${index}] cannot override the validated Claude permission policy`);
|
|
282
|
+
}
|
|
283
|
+
if (ceiling === "read-only" && permissionCapabilityOption(arg) !== undefined) {
|
|
284
|
+
throw new Error(`${field}[${index}] cannot add a permission capability to a hardened Claude role`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|