@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,463 @@
1
+ import { spawn } from "child_process";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { createRequire } from "module";
6
+ import net from "net";
7
+ import { randomUUID } from "crypto";
8
+ import { POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@ctliz/agent-intercom-core";
9
+ import { createMessageReader, writeMessage } from "./framing.ts";
10
+ import { assertCompatibleOrdinaryAdvertisement } from "./boss-contracts.ts";
11
+ import { hasExactDataKeys, isPlainDataRecord } from "./validation.ts";
12
+ import {
13
+ ensureIntercomRuntimeDir,
14
+ getAgentDirPath,
15
+ getBrokerConnectTarget,
16
+ getIntercomDirPath,
17
+ INTERCOM_PROTOCOL_NAME,
18
+ INTERCOM_PROTOCOL_VERSION,
19
+ INTERCOM_RUNTIME_FILE_MODE,
20
+ restrictIntercomRuntimeFile,
21
+ type BrokerConnectTarget,
22
+ } from "./paths.ts";
23
+
24
+ const INTERCOM_DIR = getIntercomDirPath();
25
+ const EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
26
+ const BROKER_PID = join(INTERCOM_DIR, "broker.pid");
27
+ const BROKER_SPAWN_LOCK = join(INTERCOM_DIR, "broker.spawn.lock");
28
+
29
+ type BrokerLaunchSpec =
30
+ | {
31
+ kind: "direct";
32
+ command: string;
33
+ args: string[];
34
+ }
35
+ | {
36
+ kind: "windows-launcher";
37
+ command: string;
38
+ args: string[];
39
+ launcherPath: string;
40
+ launcherCommandLine: string;
41
+ };
42
+
43
+ function sleep(ms: number): Promise<void> {
44
+ return new Promise(resolve => setTimeout(resolve, ms));
45
+ }
46
+
47
+ export function getBrokerEntryPath(moduleUrl: string = import.meta.url): string {
48
+ const moduleDir = dirname(fileURLToPath(moduleUrl));
49
+ const bundledBroker = join(moduleDir, "broker.mjs");
50
+ return existsSync(bundledBroker) ? bundledBroker : join(moduleDir, "broker.ts");
51
+ }
52
+
53
+ export function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {
54
+ // Resolve tsx via Node's module resolution so it works regardless of whether
55
+ // tsx is bundled under extensionDir/node_modules or hoisted to a workspace
56
+ // root by npm. We resolve the tsx package main entry (its "exports" field
57
+ // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next
58
+ // to it. Falls back to the legacy relative path if resolution fails.
59
+ try {
60
+ const requireFromExtension = createRequire(import.meta.url);
61
+ const tsxMain = requireFromExtension.resolve("tsx");
62
+ return join(dirname(tsxMain), "cli.mjs");
63
+ } catch {
64
+ return join(extensionDir, "node_modules", "tsx", "dist", "cli.mjs");
65
+ }
66
+ }
67
+
68
+ function quoteWindowsArg(value: string): string {
69
+ return `"${value.replace(/"/g, '""')}"`;
70
+ }
71
+
72
+ export function getWindowsHiddenLauncherPath(intercomDir: string = INTERCOM_DIR): string {
73
+ return join(intercomDir, "broker-launch.vbs");
74
+ }
75
+
76
+ function usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]): boolean {
77
+ return brokerCommand === "npx"
78
+ && brokerArgs.length === 2
79
+ && brokerArgs[0] === "--no-install"
80
+ && brokerArgs[1] === "tsx";
81
+ }
82
+
83
+ export function getWindowsBrokerCommandLine(
84
+ brokerPath: string,
85
+ extensionDir: string = EXTENSION_DIR,
86
+ nodePath: string = process.execPath,
87
+ brokerCommand = "npx",
88
+ brokerArgs: string[] = ["--no-install", "tsx"],
89
+ ): string {
90
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
91
+ return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(" ");
92
+ }
93
+
94
+ return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(" ");
95
+ }
96
+
97
+ export function getWindowsHiddenLauncherScript(commandLine: string): string {
98
+ return [
99
+ 'Set WshShell = CreateObject("WScript.Shell")',
100
+ `WshShell.Run "${commandLine.replace(/"/g, '""')}", 0, False`,
101
+ 'Set WshShell = Nothing',
102
+ '',
103
+ ].join("\r\n");
104
+ }
105
+
106
+ export function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {
107
+ if (!hasExactDataKeys(
108
+ message,
109
+ ["type", "requestId", "protocol", "version", "endpoint", "remoteAccess"],
110
+ ["capabilities"],
111
+ )) return false;
112
+ const response = message;
113
+ if (
114
+ response.type !== "health_ok"
115
+ || response.requestId !== requestId
116
+ || response.protocol !== INTERCOM_PROTOCOL_NAME
117
+ || response.version !== INTERCOM_PROTOCOL_VERSION
118
+ || response.endpoint !== "local"
119
+ ) return false;
120
+ const remoteAccess = response.remoteAccess;
121
+ if (!hasExactDataKeys(remoteAccess, ["feature", "policySemanticsVersion", "policySemanticsHash"])) return false;
122
+ const contract = remoteAccess;
123
+ const legacyCompatible = contract.feature === "remote-access-v1"
124
+ && contract.policySemanticsVersion === POLICY_SEMANTICS_VERSION
125
+ && contract.policySemanticsHash === POLICY_SEMANTICS_HASH;
126
+ if (!legacyCompatible) return false;
127
+ if (response.capabilities !== undefined) {
128
+ try {
129
+ assertCompatibleOrdinaryAdvertisement(response.capabilities);
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
134
+ return true;
135
+ }
136
+
137
+ function writeWindowsHiddenLauncher(
138
+ commandLine: string,
139
+ launcherPath: string = getWindowsHiddenLauncherPath(),
140
+ ): string {
141
+ ensureIntercomRuntimeDir(dirname(launcherPath));
142
+ writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {
143
+ encoding: "utf-8",
144
+ mode: INTERCOM_RUNTIME_FILE_MODE,
145
+ });
146
+ restrictIntercomRuntimeFile(launcherPath);
147
+ return launcherPath;
148
+ }
149
+
150
+ export function getBrokerLaunchSpec(
151
+ brokerPath: string,
152
+ brokerCommand: string,
153
+ brokerArgs: string[],
154
+ extensionDir: string = EXTENSION_DIR,
155
+ platform: NodeJS.Platform = process.platform,
156
+ intercomDir: string = INTERCOM_DIR,
157
+ nodePath: string = process.execPath,
158
+ ): BrokerLaunchSpec {
159
+ if (platform === "win32") {
160
+ const launcherPath = getWindowsHiddenLauncherPath(intercomDir);
161
+ return {
162
+ kind: "windows-launcher",
163
+ command: "wscript.exe",
164
+ args: [launcherPath],
165
+ launcherPath,
166
+ launcherCommandLine: getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs),
167
+ };
168
+ }
169
+
170
+ if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {
171
+ return {
172
+ kind: "direct",
173
+ command: nodePath,
174
+ args: [getTsxCliPath(extensionDir), brokerPath],
175
+ };
176
+ }
177
+
178
+ return {
179
+ kind: "direct",
180
+ command: brokerCommand,
181
+ args: [...brokerArgs, brokerPath],
182
+ };
183
+ }
184
+
185
+ export function getBrokerSpawnOptions(
186
+ extensionDir: string = EXTENSION_DIR,
187
+ env: NodeJS.ProcessEnv = process.env,
188
+ ): {
189
+ detached: true;
190
+ stdio: "ignore";
191
+ cwd: string;
192
+ env: NodeJS.ProcessEnv;
193
+ windowsHide: true;
194
+ } {
195
+ return {
196
+ detached: true,
197
+ stdio: "ignore",
198
+ cwd: extensionDir,
199
+ env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: "1" },
200
+ windowsHide: true,
201
+ };
202
+ }
203
+
204
+ function toError(error: unknown): Error {
205
+ return error instanceof Error ? error : new Error(String(error));
206
+ }
207
+
208
+ export async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {
209
+ ensureIntercomRuntimeDir(INTERCOM_DIR);
210
+
211
+ if (await isBrokerRunning()) {
212
+ return;
213
+ }
214
+
215
+ const ownsLock = acquireSpawnLock();
216
+ if (!ownsLock) {
217
+ await waitForBroker();
218
+ return;
219
+ }
220
+
221
+ try {
222
+ if (await isBrokerRunning()) {
223
+ return;
224
+ }
225
+ if (await checkBrokerHealth() === "incompatible") {
226
+ throw new Error(`Incompatible live intercom broker; expected ${INTERCOM_PROTOCOL_NAME} v${INTERCOM_PROTOCOL_VERSION}. Stop it explicitly during a coordinated migration.`);
227
+ }
228
+
229
+ const brokerPath = getBrokerEntryPath();
230
+ const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs);
231
+ if (launch.kind === "windows-launcher") {
232
+ writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
233
+ }
234
+ const child = spawn(launch.command, launch.args, getBrokerSpawnOptions());
235
+ child.unref();
236
+
237
+ await new Promise<void>((resolve, reject) => {
238
+ const cleanup = () => {
239
+ child.off("error", onError);
240
+ child.off("exit", onExit);
241
+ };
242
+
243
+ const onError = (error: Error) => {
244
+ cleanup();
245
+ reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));
246
+ };
247
+
248
+ const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
249
+ if (launch.kind === "windows-launcher" && code === 0 && signal === null) {
250
+ return;
251
+ }
252
+ cleanup();
253
+ if (signal) {
254
+ reject(new Error(`Intercom broker exited before startup with signal ${signal}`));
255
+ return;
256
+ }
257
+ reject(new Error(`Intercom broker exited before startup with code ${code ?? "unknown"}`));
258
+ };
259
+
260
+ child.once("error", onError);
261
+ child.once("exit", onExit);
262
+ waitForBroker().then(() => {
263
+ cleanup();
264
+ resolve();
265
+ }, (error) => {
266
+ cleanup();
267
+ reject(toError(error));
268
+ });
269
+ });
270
+ } finally {
271
+ releaseSpawnLock();
272
+ }
273
+ }
274
+
275
+ export async function stopBrokerProcess(pidFile = BROKER_PID, timeoutMs = 3000): Promise<void> {
276
+ if (!existsSync(pidFile)) return;
277
+ let pid: number;
278
+ try {
279
+ pid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
280
+ } catch {
281
+ return;
282
+ }
283
+ if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid) return;
284
+ try {
285
+ process.kill(pid, "SIGTERM");
286
+ } catch {
287
+ return;
288
+ }
289
+ const startedAt = Date.now();
290
+ while (Date.now() - startedAt < timeoutMs) {
291
+ try {
292
+ process.kill(pid, 0);
293
+ await sleep(50);
294
+ } catch {
295
+ return;
296
+ }
297
+ }
298
+ throw new Error(`Incompatible intercom broker ${pid} did not stop within ${timeoutMs}ms`);
299
+ }
300
+
301
+ async function isBrokerRunning(): Promise<boolean> {
302
+ if (await checkSocketConnectable()) {
303
+ return true;
304
+ }
305
+
306
+ if (!existsSync(BROKER_PID)) return false;
307
+
308
+ try {
309
+ const pid = parseInt(readFileSync(BROKER_PID, "utf-8").trim(), 10);
310
+ if (!Number.isFinite(pid)) return false;
311
+ process.kill(pid, 0);
312
+ return checkSocketConnectable();
313
+ } catch {
314
+ // Missing or unreadable PID state means there is no live broker to reuse.
315
+ return false;
316
+ }
317
+ }
318
+
319
+ function connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {
320
+ return typeof target === "string"
321
+ ? net.connect(target)
322
+ : net.connect({ host: target.host, port: target.port });
323
+ }
324
+
325
+ type BrokerHealth = "compatible" | "incompatible" | "unreachable";
326
+
327
+ async function checkSocketConnectable(): Promise<boolean> {
328
+ return await checkBrokerHealth() === "compatible";
329
+ }
330
+
331
+ function checkBrokerHealth(): Promise<BrokerHealth> {
332
+ return new Promise((resolve) => {
333
+ let target: BrokerConnectTarget;
334
+ try {
335
+ target = getBrokerConnectTarget();
336
+ } catch {
337
+ resolve("unreachable");
338
+ return;
339
+ }
340
+
341
+ const socket = connectToBrokerTarget(target);
342
+ const requestId = randomUUID();
343
+ const expectedStateId = typeof target === "string" ? undefined : target.stateId;
344
+ let settled = false;
345
+ const finish = (health: BrokerHealth) => {
346
+ if (settled) {
347
+ return;
348
+ }
349
+ settled = true;
350
+ clearTimeout(timeout);
351
+ socket.off("connect", onConnect);
352
+ socket.off("error", onError);
353
+ socket.off("data", reader);
354
+ socket.destroy();
355
+ resolve(health);
356
+ };
357
+ const onConnect = () => {
358
+ try {
359
+ writeMessage(socket, {
360
+ type: "health",
361
+ requestId,
362
+ ...(expectedStateId ? { stateId: expectedStateId } : {}),
363
+ });
364
+ } catch {
365
+ finish("unreachable");
366
+ }
367
+ };
368
+ const onError = () => finish("unreachable");
369
+ const reader = createMessageReader((message) => {
370
+ if (isBrokerHealthOkMessage(message, requestId)) {
371
+ finish("compatible");
372
+ return;
373
+ }
374
+ if (
375
+ isPlainDataRecord(message)
376
+ && message.type === "health_ok"
377
+ && message.requestId === requestId
378
+ ) {
379
+ finish("incompatible");
380
+ return;
381
+ }
382
+ finish("unreachable");
383
+ }, () => finish("unreachable"));
384
+ socket.on("connect", onConnect);
385
+ socket.on("error", onError);
386
+ socket.on("data", reader);
387
+ const timeout = setTimeout(() => finish("unreachable"), 1000);
388
+ });
389
+ }
390
+
391
+ function acquireSpawnLock(): boolean {
392
+ const maxRetries = 5;
393
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
394
+ try {
395
+ writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\n${Date.now()}\n`, {
396
+ flag: "wx",
397
+ mode: INTERCOM_RUNTIME_FILE_MODE,
398
+ });
399
+ restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);
400
+ return true;
401
+ } catch (error) {
402
+ if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "EEXIST") {
403
+ throw error;
404
+ }
405
+ if (isSpawnLockStale()) {
406
+ try {
407
+ unlinkSync(BROKER_SPAWN_LOCK);
408
+ } catch {
409
+ // If we can't delete the stale lock, retry a few times before giving up
410
+ }
411
+ continue;
412
+ }
413
+ return false;
414
+ }
415
+ }
416
+ return false;
417
+ }
418
+
419
+ function isSpawnLockStale(): boolean {
420
+ if (!existsSync(BROKER_SPAWN_LOCK)) {
421
+ return false;
422
+ }
423
+
424
+ try {
425
+ const [pidLine = "", createdAtLine = "0"] = readFileSync(BROKER_SPAWN_LOCK, "utf-8").trim().split("\n");
426
+ const pid = Number.parseInt(pidLine, 10);
427
+ const createdAt = Number.parseInt(createdAtLine, 10);
428
+ const ageMs = Date.now() - createdAt;
429
+
430
+ if (Number.isFinite(pid)) {
431
+ try {
432
+ process.kill(pid, 0);
433
+ } catch {
434
+ // The process that created the lock is gone.
435
+ return true;
436
+ }
437
+ }
438
+
439
+ return !Number.isFinite(createdAt) || ageMs > 10_000;
440
+ } catch {
441
+ // Unreadable lock contents are treated as stale so a new broker can start.
442
+ return true;
443
+ }
444
+ }
445
+
446
+ function releaseSpawnLock(): void {
447
+ try {
448
+ unlinkSync(BROKER_SPAWN_LOCK);
449
+ } catch {
450
+ // Another cleanup path may already have removed the lock.
451
+ }
452
+ }
453
+
454
+ async function waitForBroker(timeoutMs = 5000): Promise<void> {
455
+ const start = Date.now();
456
+ while (Date.now() - start < timeoutMs) {
457
+ if (await checkSocketConnectable()) {
458
+ return;
459
+ }
460
+ await sleep(100);
461
+ }
462
+ throw new Error("Broker failed to start within timeout");
463
+ }
@@ -0,0 +1,132 @@
1
+ import { types as nodeUtilTypes } from "node:util";
2
+
3
+ /** Reject accessors, inherited records, and Proxies before reading wire data. */
4
+ export function isPlainDataRecord(value: unknown): value is Record<string, unknown> {
5
+ if (
6
+ typeof value !== "object"
7
+ || value === null
8
+ || nodeUtilTypes.isProxy(value)
9
+ || Array.isArray(value)
10
+ || Object.getPrototypeOf(value) !== Object.prototype
11
+ ) {
12
+ return false;
13
+ }
14
+ try {
15
+ return Reflect.ownKeys(value).every((key) => {
16
+ if (typeof key !== "string") return false;
17
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
18
+ return descriptor !== undefined && descriptor.enumerable && Object.hasOwn(descriptor, "value");
19
+ });
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ /** Require a plain record with exactly the declared own data properties. */
26
+ export function hasExactDataKeys(
27
+ value: unknown,
28
+ required: readonly string[],
29
+ optional: readonly string[] = [],
30
+ ): value is Record<string, unknown> {
31
+ if (!isPlainDataRecord(value)) return false;
32
+ const allowed = new Set([...required, ...optional]);
33
+ const keys = Reflect.ownKeys(value);
34
+ return keys.length >= required.length
35
+ && keys.every((key) => typeof key === "string" && allowed.has(key))
36
+ && required.every((key) => Object.hasOwn(value, key));
37
+ }
38
+
39
+ /**
40
+ * Preflight an authoritative nested graph without invoking Proxy traps or
41
+ * accessors. Core parsers may run only after this adapter-owned check passes.
42
+ */
43
+ export function assertPlainDataGraph(value: unknown, path = "$", seen = new WeakSet<object>()): void {
44
+ if ((typeof value !== "object" && typeof value !== "function") || value === null) return;
45
+ if (nodeUtilTypes.isProxy(value)) {
46
+ throw new Error(`${path} must not contain a Proxy`);
47
+ }
48
+ if (seen.has(value)) {
49
+ throw new Error(`${path} must not contain a cycle or repeated object reference`);
50
+ }
51
+ seen.add(value);
52
+
53
+ if (Array.isArray(value)) {
54
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
55
+ throw new Error(`${path} must use the exact Array prototype`);
56
+ }
57
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
58
+ if (lengthDescriptor === undefined || !Object.hasOwn(lengthDescriptor, "value")) {
59
+ throw new Error(`${path}.length must be an own data property`);
60
+ }
61
+ const length = lengthDescriptor.value;
62
+ const descriptors = new Map<number, PropertyDescriptor>();
63
+ for (const key of Reflect.ownKeys(value)) {
64
+ if (key === "length") continue;
65
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
66
+ const index = Number(key);
67
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
68
+ throw new Error(`${path} must not contain non-index properties`);
69
+ }
70
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
71
+ if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
72
+ throw new Error(`${path}[${index}] must be an enumerable own data property`);
73
+ }
74
+ descriptors.set(index, descriptor);
75
+ }
76
+ if (descriptors.size !== length) throw new Error(`${path} must be dense`);
77
+ for (let index = 0; index < length; index += 1) {
78
+ const descriptor = descriptors.get(index);
79
+ if (descriptor === undefined) throw new Error(`${path} must be dense`);
80
+ assertPlainDataGraph(descriptor.value, `${path}[${index}]`, seen);
81
+ }
82
+ return;
83
+ }
84
+
85
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
86
+ throw new Error(`${path} must be a plain data object`);
87
+ }
88
+ for (const key of Reflect.ownKeys(value)) {
89
+ if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`);
90
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
91
+ if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) {
92
+ throw new Error(`${path}.${key} must be an enumerable own data property`);
93
+ }
94
+ assertPlainDataGraph(descriptor.value, `${path}.${key}`, seen);
95
+ }
96
+ }
97
+
98
+ /** Array.prototype.every skips holes; wire arrays must instead be exact and dense. */
99
+ export function isDenseArrayOf<T>(
100
+ value: unknown,
101
+ predicate: (entry: unknown, index: number) => entry is T,
102
+ ): value is T[] {
103
+ if (
104
+ typeof value !== "object"
105
+ || value === null
106
+ || nodeUtilTypes.isProxy(value)
107
+ || !Array.isArray(value)
108
+ || Object.getPrototypeOf(value) !== Array.prototype
109
+ ) return false;
110
+ try {
111
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
112
+ if (lengthDescriptor === undefined || !Object.hasOwn(lengthDescriptor, "value")) return false;
113
+ const length = lengthDescriptor.value;
114
+ const entries = new Map<number, unknown>();
115
+ for (const key of Reflect.ownKeys(value)) {
116
+ if (key === "length") continue;
117
+ if (typeof key !== "string") return false;
118
+ const index = Number(key);
119
+ if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) return false;
120
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
121
+ if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) return false;
122
+ entries.set(index, descriptor.value);
123
+ }
124
+ if (entries.size !== length) return false;
125
+ for (let index = 0; index < length; index += 1) {
126
+ if (!predicate(entries.get(index), index)) return false;
127
+ }
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ }