@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,140 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
const repositoryRoot = resolve(import.meta.dirname, "..");
|
|
8
|
+
const corePackage = "@ctliz/agent-intercom-core";
|
|
9
|
+
const coreDevSpec = "git+https://github.com/ctliz/agent-intercom-core.git#37e074970e2a9de32a16fc325607c3b476b0bd45";
|
|
10
|
+
const coreConsumers = new Set([
|
|
11
|
+
"broker.mjs",
|
|
12
|
+
"cci.mjs",
|
|
13
|
+
"ccim.mjs",
|
|
14
|
+
"claude-server.mjs",
|
|
15
|
+
"worker-daemon.mjs",
|
|
16
|
+
]);
|
|
17
|
+
const bundles = [...coreConsumers, "inbox-monitor.mjs"];
|
|
18
|
+
|
|
19
|
+
function tarString(buffer, offset, length) {
|
|
20
|
+
const end = buffer.indexOf(0, offset);
|
|
21
|
+
return buffer.subarray(offset, end === -1 || end > offset + length ? offset + length : end).toString("utf8");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readTarEntries(archivePath) {
|
|
25
|
+
const archive = gunzipSync(readFileSync(archivePath));
|
|
26
|
+
const entries = new Map();
|
|
27
|
+
for (let offset = 0; offset + 512 <= archive.length;) {
|
|
28
|
+
const header = archive.subarray(offset, offset + 512);
|
|
29
|
+
if (header.every((byte) => byte === 0)) break;
|
|
30
|
+
const name = tarString(header, 0, 100);
|
|
31
|
+
const prefix = tarString(header, 345, 155);
|
|
32
|
+
const path = prefix ? `${prefix}/${name}` : name;
|
|
33
|
+
const sizeText = tarString(header, 124, 12).trim();
|
|
34
|
+
const size = Number.parseInt(sizeText || "0", 8);
|
|
35
|
+
if (!Number.isSafeInteger(size)) throw new Error(`Invalid tar size for ${path}`);
|
|
36
|
+
const contentOffset = offset + 512;
|
|
37
|
+
entries.set(path, archive.subarray(contentOffset, contentOffset + size));
|
|
38
|
+
offset = contentOffset + Math.ceil(size / 512) * 512;
|
|
39
|
+
}
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createPackageArchive(temp) {
|
|
44
|
+
const result = spawnSync("npm", [
|
|
45
|
+
"pack",
|
|
46
|
+
"--ignore-scripts",
|
|
47
|
+
"--json",
|
|
48
|
+
"--pack-destination",
|
|
49
|
+
temp,
|
|
50
|
+
], {
|
|
51
|
+
cwd: repositoryRoot,
|
|
52
|
+
env: { ...process.env, npm_config_cache: join(temp, "npm-cache") },
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
});
|
|
55
|
+
if (result.error) throw result.error;
|
|
56
|
+
if (result.status !== 0) throw new Error(`npm pack failed: ${result.stderr || result.stdout}`);
|
|
57
|
+
const packed = JSON.parse(result.stdout);
|
|
58
|
+
if (!Array.isArray(packed) || packed.length !== 1 || typeof packed[0]?.filename !== "string") {
|
|
59
|
+
throw new Error("npm pack did not report exactly one package archive");
|
|
60
|
+
}
|
|
61
|
+
return join(temp, packed[0].filename);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const archiveArgument = process.argv.indexOf("--archive");
|
|
65
|
+
const suppliedArchive = archiveArgument === -1 ? undefined : process.argv[archiveArgument + 1];
|
|
66
|
+
if (archiveArgument !== -1 && !suppliedArchive) throw new Error("--archive requires a package tarball path");
|
|
67
|
+
const temp = suppliedArchive ? undefined : mkdtempSync(join(tmpdir(), "claude-intercom-pack-check-"));
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const archivePath = suppliedArchive ? resolve(suppliedArchive) : createPackageArchive(temp);
|
|
71
|
+
const entries = readTarEntries(archivePath);
|
|
72
|
+
const manifestEntry = entries.get("package/package.json");
|
|
73
|
+
if (!manifestEntry) throw new Error("Packed adapter is missing package.json");
|
|
74
|
+
const manifest = JSON.parse(manifestEntry.toString("utf8"));
|
|
75
|
+
if (manifest.peerDependencies?.[corePackage] !== "0.1.0") throw new Error("Packed adapter must require Core peer 0.1.0");
|
|
76
|
+
if (manifest.peerDependenciesMeta?.[corePackage]?.optional === true) throw new Error("Packed Core peer must not be optional");
|
|
77
|
+
if (manifest.dependencies?.[corePackage] !== undefined) throw new Error("Packed adapter must not install a private Core dependency");
|
|
78
|
+
if (manifest.devDependencies?.[corePackage] !== coreDevSpec) throw new Error("Packed adapter lost the approved Core build commit");
|
|
79
|
+
if ([...entries.keys()].some((path) => path.includes("node_modules/@ctliz/agent-intercom-core/"))) {
|
|
80
|
+
throw new Error("Packed adapter contains a private Core module tree");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 1. Assert Claude Plugin manifest exists and is valid
|
|
84
|
+
const pluginEntry = entries.get("package/.claude-plugin/plugin.json");
|
|
85
|
+
if (!pluginEntry) throw new Error("Packed adapter is missing .claude-plugin/plugin.json");
|
|
86
|
+
const plugin = JSON.parse(pluginEntry.toString("utf8"));
|
|
87
|
+
if (plugin.name !== "claude-intercom") throw new Error(`Unexpected plugin name: ${plugin.name}`);
|
|
88
|
+
|
|
89
|
+
// 2. Assert Plugin -> MCP configuration exists and points to real dist bundle
|
|
90
|
+
if (typeof plugin.mcpServers !== "string") throw new Error("plugin.json missing mcpServers declaration");
|
|
91
|
+
const mcpRelPath = plugin.mcpServers.replace(/^\.\//, "");
|
|
92
|
+
const mcpEntry = entries.get(`package/${mcpRelPath}`);
|
|
93
|
+
if (!mcpEntry) throw new Error(`Packed adapter missing plugin mcpServers file: ${mcpRelPath}`);
|
|
94
|
+
const mcpConfig = JSON.parse(mcpEntry.toString("utf8"));
|
|
95
|
+
const claudeServerArg = mcpConfig.mcpServers?.["claude-intercom"]?.args?.[0];
|
|
96
|
+
if (!claudeServerArg || !claudeServerArg.includes("/dist/claude-server.mjs")) {
|
|
97
|
+
throw new Error(`MCP config does not reference dist/claude-server.mjs: ${claudeServerArg}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 3. Assert Plugin -> Monitors configuration exists and points to real dist bundle
|
|
101
|
+
if (typeof plugin.monitors !== "string") throw new Error("plugin.json missing monitors declaration");
|
|
102
|
+
const monitorsRelPath = plugin.monitors.replace(/^\.\//, "");
|
|
103
|
+
const monitorsEntry = entries.get(`package/${monitorsRelPath}`);
|
|
104
|
+
if (!monitorsEntry) throw new Error(`Packed adapter missing plugin monitors file: ${monitorsRelPath}`);
|
|
105
|
+
const monitorsConfig = JSON.parse(monitorsEntry.toString("utf8"));
|
|
106
|
+
if (!Array.isArray(monitorsConfig) || monitorsConfig.length === 0) {
|
|
107
|
+
throw new Error("monitors.json is empty or not an array");
|
|
108
|
+
}
|
|
109
|
+
const inboxMonitor = monitorsConfig.find((m) => m.name === "intercom-inbox");
|
|
110
|
+
if (!inboxMonitor || !inboxMonitor.command?.includes("/dist/inbox-monitor.mjs")) {
|
|
111
|
+
throw new Error(`monitors.json missing intercom-inbox command referencing dist/inbox-monitor.mjs: ${JSON.stringify(inboxMonitor)}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4. Assert Plugin -> Skills & Commands exist in packed archive
|
|
115
|
+
if (typeof plugin.skills !== "string") throw new Error("plugin.json missing skills declaration");
|
|
116
|
+
const skillEntry = entries.get("package/skills/claude-intercom/SKILL.md");
|
|
117
|
+
if (!skillEntry) throw new Error("Packed adapter missing skills/claude-intercom/SKILL.md");
|
|
118
|
+
|
|
119
|
+
if (typeof plugin.commands !== "string") throw new Error("plugin.json missing commands declaration");
|
|
120
|
+
const commandEntry1 = entries.get("package/commands/intercom.md");
|
|
121
|
+
const commandEntry2 = entries.get("package/commands/intercom-id.md");
|
|
122
|
+
if (!commandEntry1 || !commandEntry2) throw new Error("Packed adapter missing commands markdown files");
|
|
123
|
+
|
|
124
|
+
// 5. Assert all dist bundles exist and adhere to Core externalization
|
|
125
|
+
for (const bundle of bundles) {
|
|
126
|
+
const entry = entries.get(`package/dist/${bundle}`);
|
|
127
|
+
if (!entry) throw new Error(`Packed adapter is missing dist/${bundle}`);
|
|
128
|
+
const source = entry.toString("utf8");
|
|
129
|
+
if (source.includes("node_modules/@ctliz/agent-intercom-core/") || /var POLICY_SEMANTICS_VERSION\s*=/.test(source)) {
|
|
130
|
+
throw new Error(`Packed dist/${bundle} embeds a private Core copy`);
|
|
131
|
+
}
|
|
132
|
+
if (coreConsumers.has(bundle) && !/from ["']@ctliz\/agent-intercom-core(?:\/[A-Za-z/-]+)?["']/.test(source)) {
|
|
133
|
+
throw new Error(`Packed dist/${bundle} does not retain its Core peer import`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`Verified complete plugin+MCP+monitors+dist chain and ${bundles.length} packed bundles using Core peer 0.1.0`);
|
|
138
|
+
} finally {
|
|
139
|
+
if (temp) rmSync(temp, { recursive: true, force: true });
|
|
140
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: claude-intercom
|
|
3
|
+
description: |
|
|
4
|
+
Coordinate with local Pi, Codex, or Claude coding-agent sessions through
|
|
5
|
+
claude-intercom MCP tools. Use for same-machine planner-worker workflows,
|
|
6
|
+
direct peer questions, cross-session context sharing, and checking or
|
|
7
|
+
replying to pending asks.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Claude Intercom
|
|
11
|
+
|
|
12
|
+
Use this skill when you need to coordinate with another local coding-agent
|
|
13
|
+
session through the `claude-intercom` MCP server. It speaks the same broker
|
|
14
|
+
protocol as `pi-intercom` and `codex-intercom`, so Pi, Codex, and Claude
|
|
15
|
+
sessions can all message each other.
|
|
16
|
+
|
|
17
|
+
A plain Claude Code MCP session does not receive unsolicited turns. Inbound
|
|
18
|
+
messages are queued by the MCP server while it is running. Check
|
|
19
|
+
`intercom_pending` at natural boundaries: before starting delegated work, and
|
|
20
|
+
when you expect a response.
|
|
21
|
+
|
|
22
|
+
For true wake-on-message behavior, use or recommend a **wakeable worker**
|
|
23
|
+
(`cci` or `claude-intercom-worker`). Messages to a worker start a fresh
|
|
24
|
+
headless `claude -p` turn that resumes the worker's own conversation, so the
|
|
25
|
+
worker can read files, run commands, and reply on its own. Normal `cci` workers
|
|
26
|
+
automatically load the packaged Intercom MCP server, including under isolated
|
|
27
|
+
proxy-backed Claude profiles; `ccim` safe mode intentionally does not.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
- `intercom_whoami`: show this session's intercom ID, name, cwd, and model.
|
|
32
|
+
- `intercom_team`: show the current orchestrator manager and live same-manager coworkers.
|
|
33
|
+
- `intercom_status`: connection, active session count, unread messages, pending asks.
|
|
34
|
+
- `intercom_list`: list connected Pi, Codex, and Claude sessions.
|
|
35
|
+
- `intercom_set_summary`: publish a short discoverable status.
|
|
36
|
+
- `intercom_send`: fire-and-forget direct message.
|
|
37
|
+
- `intercom_ask`: send a question and wait for the target's reply (blocking, bounded).
|
|
38
|
+
- `intercom_pending`: read queued inbound messages and pending asks.
|
|
39
|
+
- `intercom_reply`: reply to a pending inbound ask.
|
|
40
|
+
|
|
41
|
+
## Workflow
|
|
42
|
+
|
|
43
|
+
1. Call `intercom_status` or `intercom_whoami` to confirm this session is connected.
|
|
44
|
+
2. Call `intercom_set_summary` with a concise status so peers can discover your role.
|
|
45
|
+
3. If this is an orchestrator-owned coworker, call `intercom_team` to get the manager and sibling targets without searching globally. Otherwise use `intercom_list`.
|
|
46
|
+
4. Use `intercom_send` for non-blocking updates and handoffs.
|
|
47
|
+
5. Use `intercom_ask` only when you need the answer before continuing. Assignments, progress/status checkpoints, notifications, and completion reports use `intercom_send`.
|
|
48
|
+
6. Call `intercom_pending` before ending a coordination turn, then answer blocking asks with `intercom_reply`.
|
|
49
|
+
|
|
50
|
+
## Patterns
|
|
51
|
+
|
|
52
|
+
Planner delegates to a wakeable worker without blocking:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
intercom_send({
|
|
56
|
+
to: "worker",
|
|
57
|
+
message: "Task-3: add retry logic in src/api/client.ts. Ask if the retry scope is unclear."
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Ask a worker and wait for its answer (the worker's final message comes back as the reply):
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
intercom_ask({
|
|
65
|
+
to: "worker",
|
|
66
|
+
message: "Inspect the failing test in test/auth.test.ts and reply with the most likely cause."
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Reply to an inbound ask:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
intercom_pending({ mark_read: false })
|
|
74
|
+
intercom_reply({ message: "Use GET/PUT/DELETE only, max 3 retries." })
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When multiple asks are pending, select the sender with `to`. If that sender has multiple unresolved asks, select `oldest` or `latest` without using protocol IDs:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
intercom_reply({ to: "planner", which: "oldest", message: "Proceed." })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Keep at most one unresolved `intercom_ask` to the same recipient. Use `intercom_send` for non-blocking follow-ups and every progress/status request.
|
|
84
|
+
|
|
85
|
+
## Boundaries
|
|
86
|
+
|
|
87
|
+
- Do not assume push delivery into a plain MCP session. Check `intercom_pending`.
|
|
88
|
+
- A plain MCP session cannot wake itself from idle. Use a wakeable worker (`cci`) for wake-on-message.
|
|
89
|
+
- Do not use `intercom_ask` to poll files, ports, or process completion — use normal shell checks.
|
|
90
|
+
- Keep messages concise; include file paths, command-output summaries, and decision options when useful.
|
package/types.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BossControlEnvelope,
|
|
3
|
+
BossParticipantBinding,
|
|
4
|
+
BossParticipantCredentialEnvelope,
|
|
5
|
+
BossPrivatePrincipal,
|
|
6
|
+
BossRunFeatureContract,
|
|
7
|
+
BossWorkerIdentityV2,
|
|
8
|
+
BrokerCapabilityAdvertisement,
|
|
9
|
+
ParticipantState,
|
|
10
|
+
} from "@ctliz/agent-intercom-core/boss";
|
|
11
|
+
|
|
12
|
+
export interface BossLiveWorkerAuthority {
|
|
13
|
+
identity: BossWorkerIdentityV2;
|
|
14
|
+
state: ParticipantState;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BossSessionMetadata {
|
|
18
|
+
binding: BossParticipantBinding;
|
|
19
|
+
principal: BossPrivatePrincipal;
|
|
20
|
+
/** Broker-authenticated live worker identity, independent of the worker registry projection. */
|
|
21
|
+
liveWorker: BossLiveWorkerAuthority;
|
|
22
|
+
featureContract: BossRunFeatureContract;
|
|
23
|
+
policySemanticsHash: string;
|
|
24
|
+
capabilityDigest: string;
|
|
25
|
+
brokerIdentityVerified: true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BossRegistrationRequest {
|
|
29
|
+
featureContract: BossRunFeatureContract;
|
|
30
|
+
capabilityDigest: string;
|
|
31
|
+
credential: BossParticipantCredentialEnvelope;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SessionInfo {
|
|
35
|
+
id: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
cwd: string;
|
|
38
|
+
model: string;
|
|
39
|
+
pid: number;
|
|
40
|
+
startedAt: number;
|
|
41
|
+
lastActivity: number;
|
|
42
|
+
status?: string;
|
|
43
|
+
peerUid?: number;
|
|
44
|
+
trustedLocal?: boolean;
|
|
45
|
+
origin?: "local" | "remote";
|
|
46
|
+
remoteHostId?: string;
|
|
47
|
+
parentSessionId?: string;
|
|
48
|
+
rootSessionId?: string;
|
|
49
|
+
generation?: number;
|
|
50
|
+
canDelegate?: boolean;
|
|
51
|
+
depth?: number;
|
|
52
|
+
maxDepth?: number;
|
|
53
|
+
maxChildren?: number;
|
|
54
|
+
/** Broker-authoritative metadata; absent for every ordinary/legacy session. */
|
|
55
|
+
boss?: BossSessionMetadata;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface Message {
|
|
59
|
+
id: string;
|
|
60
|
+
timestamp: number;
|
|
61
|
+
replyTo?: string;
|
|
62
|
+
expectsReply?: boolean;
|
|
63
|
+
/** Internal typed control. Model-facing text tools cannot construct this. */
|
|
64
|
+
control?: BossControlEnvelope;
|
|
65
|
+
content: {
|
|
66
|
+
text: string;
|
|
67
|
+
attachments?: Attachment[];
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Attachment {
|
|
72
|
+
type: "file" | "snippet" | "context";
|
|
73
|
+
name: string;
|
|
74
|
+
content: string;
|
|
75
|
+
language?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SessionRegistration = Omit<
|
|
79
|
+
SessionInfo,
|
|
80
|
+
"id" | "peerUid" | "trustedLocal" | "origin" | "remoteHostId" | "parentSessionId" | "rootSessionId" | "generation" | "canDelegate" | "depth" | "maxDepth" | "maxChildren" | "boss"
|
|
81
|
+
> & {
|
|
82
|
+
/** Ephemeral identity shared only by reconnects from one live runtime. */
|
|
83
|
+
runtimeInstanceId?: string;
|
|
84
|
+
/** Requested Boss enrollment. The dormant Stage-B broker rejects it closed. */
|
|
85
|
+
boss?: BossRegistrationRequest;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export interface RemoteEnrollmentAccess {
|
|
89
|
+
enrollmentToken: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface RemoteSessionAccess {
|
|
93
|
+
sessionCredential: string;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
generation: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type RemoteRegistrationAccess = RemoteEnrollmentAccess | RemoteSessionAccess;
|
|
99
|
+
|
|
100
|
+
export interface RemoteAccessMetadata {
|
|
101
|
+
origin: "remote";
|
|
102
|
+
remoteHostId: string;
|
|
103
|
+
parentSessionId: string;
|
|
104
|
+
rootSessionId: string;
|
|
105
|
+
generation: number;
|
|
106
|
+
canDelegate: boolean;
|
|
107
|
+
depth: number;
|
|
108
|
+
maxDepth: number;
|
|
109
|
+
maxChildren: number;
|
|
110
|
+
sessionCredential?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface RemotePrincipalSummary {
|
|
114
|
+
id: string;
|
|
115
|
+
name: string;
|
|
116
|
+
parentSessionId: string;
|
|
117
|
+
rootSessionId: string;
|
|
118
|
+
remoteHostId: string;
|
|
119
|
+
generation: number;
|
|
120
|
+
policy: "remote-tree";
|
|
121
|
+
canDelegate: boolean;
|
|
122
|
+
depth: number;
|
|
123
|
+
maxDepth: number;
|
|
124
|
+
maxChildren: number;
|
|
125
|
+
state: "active" | "revoked";
|
|
126
|
+
expiresAt: number;
|
|
127
|
+
createdAt: number;
|
|
128
|
+
updatedAt: number;
|
|
129
|
+
connected: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface RemoteAccessContract {
|
|
133
|
+
feature: "remote-access-v1";
|
|
134
|
+
policySemanticsVersion: number;
|
|
135
|
+
policySemanticsHash: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const DELIVERY_FAILURE_CODES = [
|
|
139
|
+
"INVALID_MESSAGE",
|
|
140
|
+
"SESSION_NOT_FOUND",
|
|
141
|
+
"AMBIGUOUS_TARGET",
|
|
142
|
+
"SENDER_NOT_FOUND",
|
|
143
|
+
"INVALID_REPLY_TARGET",
|
|
144
|
+
"MUTUAL_ASK",
|
|
145
|
+
"ASK_ALREADY_PENDING",
|
|
146
|
+
"DUPLICATE_MESSAGE_ID",
|
|
147
|
+
"CONFLICTING_MESSAGE_ID",
|
|
148
|
+
"TOO_MANY_PENDING_DELIVERIES",
|
|
149
|
+
"TOO_MANY_PENDING_ASKS",
|
|
150
|
+
"RECIPIENT_DISCONNECTED",
|
|
151
|
+
"SENDER_DISCONNECTED",
|
|
152
|
+
"DELIVERY_TIMEOUT",
|
|
153
|
+
"CONTROL_DISPATCH_UNAVAILABLE",
|
|
154
|
+
] as const;
|
|
155
|
+
|
|
156
|
+
export type DeliveryFailureCode = (typeof DELIVERY_FAILURE_CODES)[number];
|
|
157
|
+
|
|
158
|
+
export type BrokerErrorCode =
|
|
159
|
+
| "PROTOCOL_MISMATCH"
|
|
160
|
+
| "INVALID_REQUEST"
|
|
161
|
+
| "SESSION_ID_IN_USE"
|
|
162
|
+
| "ACCESS_DENIED"
|
|
163
|
+
| "BOSS_FEATURE_UNAVAILABLE"
|
|
164
|
+
| "REMOTE_ACCESS_INCOMPATIBLE"
|
|
165
|
+
| "RATE_LIMITED"
|
|
166
|
+
| "TOO_MANY_SESSIONS";
|
|
167
|
+
|
|
168
|
+
export type AskCancellationReason =
|
|
169
|
+
| "cancelled"
|
|
170
|
+
| "expired"
|
|
171
|
+
| "delivery_failed"
|
|
172
|
+
| "session_disconnected"
|
|
173
|
+
| "authorization_revoked";
|
|
174
|
+
|
|
175
|
+
export type ClientMessage =
|
|
176
|
+
| { type: "health"; requestId: string; stateId?: string }
|
|
177
|
+
| { type: "register"; protocol: string; version: number; session: SessionRegistration; sessionId?: string; stateId?: string; access?: RemoteRegistrationAccess; scopeId?: string }
|
|
178
|
+
| { type: "access_control"; requestId: string; adminToken: string; action: "issue_enrollment"; enrollment: { name: string; parentSessionId: string; rootSessionId: string; remoteHostId: string; ttlMs?: number; expiresAt?: number; canDelegate?: boolean; maxDepth?: number; maxChildren?: number } }
|
|
179
|
+
| { type: "access_control"; requestId: string; adminToken: string; action: "revoke_subtree"; principalId: string }
|
|
180
|
+
| { type: "access_control"; requestId: string; adminToken: string; action: "inspect_tree"; principalId: string }
|
|
181
|
+
| { type: "access_control"; requestId: string; adminToken: string; action: "adopt_subtree"; principalId: string; newParentSessionId: string }
|
|
182
|
+
| { type: "access_control"; requestId: string; access: RemoteSessionAccess; action: "issue_child_enrollment"; enrollment: { name: string; ttlMs?: number; expiresAt?: number; canDelegate?: boolean; maxDepth?: number; maxChildren?: number } }
|
|
183
|
+
| { type: "access_control"; requestId: string; access: RemoteSessionAccess; action: "inspect_tree"; principalId?: string }
|
|
184
|
+
| { type: "unregister"; preserveAsks?: boolean }
|
|
185
|
+
| { type: "list"; requestId: string }
|
|
186
|
+
| { type: "send"; to: string; message: Message }
|
|
187
|
+
| { type: "message_received"; deliveryId: string }
|
|
188
|
+
| { type: "message_rejected"; deliveryId: string; code: "CONFLICTING_MESSAGE_ID"; reason: string }
|
|
189
|
+
| { type: "defer_ask"; requestId: string; messageId: string }
|
|
190
|
+
| { type: "cancel_ask"; requestId: string; messageId: string }
|
|
191
|
+
| { type: "presence"; name?: string; status?: string; model?: string };
|
|
192
|
+
|
|
193
|
+
export type BrokerMessage =
|
|
194
|
+
| { type: "health_ok"; requestId: string; protocol: string; version: number; endpoint: "local" | "remote"; remoteAccess?: RemoteAccessContract; capabilities?: BrokerCapabilityAdvertisement }
|
|
195
|
+
| { type: "registered"; sessionId: string; protocol: string; version: number; remoteAccess?: RemoteAccessContract; access?: RemoteAccessMetadata; capabilities?: BrokerCapabilityAdvertisement; boss?: BossSessionMetadata }
|
|
196
|
+
| { type: "access_control_result"; requestId: string; action: "issue_enrollment"; enrollmentToken: string; expiresAt: number }
|
|
197
|
+
| { type: "access_control_result"; requestId: string; action: "revoke_subtree"; changedPrincipalIds: string[] }
|
|
198
|
+
| { type: "access_control_result"; requestId: string; action: "inspect_tree"; principals: RemotePrincipalSummary[] }
|
|
199
|
+
| { type: "access_control_result"; requestId: string; action: "adopt_subtree"; principals: RemotePrincipalSummary[] }
|
|
200
|
+
| { type: "access_control_result"; requestId: string; action: "issue_child_enrollment"; enrollmentToken: string; expiresAt: number; parentSessionId: string }
|
|
201
|
+
| { type: "sessions"; requestId: string; sessions: SessionInfo[] }
|
|
202
|
+
| { type: "message"; deliveryId: string; from: SessionInfo; message: Message }
|
|
203
|
+
| { type: "presence_update"; session: SessionInfo }
|
|
204
|
+
| { type: "session_joined"; session: SessionInfo }
|
|
205
|
+
| { type: "session_left"; sessionId: string }
|
|
206
|
+
| { type: "error"; code: BrokerErrorCode; error: string }
|
|
207
|
+
| { type: "delivery_accepted"; messageId: string; deliveryId: string }
|
|
208
|
+
| { type: "delivered"; messageId: string; deliveryId: string }
|
|
209
|
+
| { type: "delivery_failed"; messageId: string; accepted: boolean; code: DeliveryFailureCode; reason: string }
|
|
210
|
+
| { type: "ask_deferred"; messageId: string; fromSessionId: string }
|
|
211
|
+
| { type: "ask_cancelled"; messageId: string; fromSessionId: string; reason: AskCancellationReason }
|
|
212
|
+
| { type: "ask_control_result"; requestId: string; action: "defer" | "cancel"; messageId: string; applied: boolean };
|