cc-peer 0.0.0 → 1.0.0
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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/bin/cc-peer.cjs +5 -0
- package/dist/bin/cc-peer.d.cts +1 -0
- package/dist/bin/cc-peer.d.mts +1 -0
- package/dist/bin/cc-peer.mjs +6 -0
- package/dist/cc-peer.cjs +5 -0
- package/dist/cc-peer.d.cts +3 -0
- package/dist/cc-peer.d.mts +3 -0
- package/dist/cc-peer.mjs +4 -0
- package/docs/PROTOCOL.md +193 -0
- package/package.json +107 -2
- package/schemas/artifact-replies-yielded.schema.json +47 -0
- package/schemas/auth-line.schema.json +19 -0
- package/schemas/envelope-attributes.schema.json +38 -0
- package/schemas/notify-when-idle.schema.json +42 -0
- package/schemas/peer-idle-notice.schema.json +62 -0
- package/schemas/peer-key-file.schema.json +24 -0
- package/schemas/peer-message-status.schema.json +74 -0
- package/schemas/registry-entry.schema.json +124 -0
- package/schemas/unyield-artifact-replies.schema.json +50 -0
- package/schemas/user-frame.schema.json +95 -0
- package/schemas/yield-artifact-replies.schema.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph Mearman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# cc-peer
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/cc-peer) [](https://www.npmjs.com/package/cc-peer) [](https://github.com/ExaDev/cc-peer/actions)
|
|
4
|
+
|
|
5
|
+
Talk to the Claude Code instances running on your machine, from any Node application: send messages, register as a named peer other sessions can discover and message, receive replies and delivery receipts, and subscribe to idle notifications. Ships a REST facade you can run with `npx cc-peer`.
|
|
6
|
+
|
|
7
|
+
> **Unofficial.** This SDK speaks Claude Code's local cross-session peer protocol, which was reverse-engineered and verified against Claude Code 2.1.269. It is not affiliated with or endorsed by Anthropic, and the protocol may change without notice between Claude Code releases.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Claude Code sessions are isolated: each interactive session binds a private Unix socket, and the only first-party way in is another Claude Code session's `SendMessage`. `cc-peer` opens that door to everything else — build tooling, agents in other harnesses, dashboards, shell scripts — with the protocol's own consent model intact (permission-mode attestation, hold-for-review, delivery receipts).
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
- **Discovery**: live sessions publish a registry at `~/.claude/sessions/<pid>.json`; `cc-peer` reads it, verifies each entry's socket and process, and can register itself there so real Claude sessions see it by name in `ListAgents`.
|
|
16
|
+
- **Transport**: per-exchange Unix-socket connections carrying two newline-delimited JSON lines (a bearer-token auth line, then the frame), authenticated with per-session key files.
|
|
17
|
+
- **Consent**: unattested messages land in the recipient's hold-for-review dialog; attested ones deliver directly. Delivery status comes back as receipts (`held`, `delivered`, `denied`, `expired`, `dropped` with reasons).
|
|
18
|
+
- **Idle subscriptions**: ask any session to notify you when it next goes idle (or exits).
|
|
19
|
+
|
|
20
|
+
The full wire reference for implementing the protocol yourself lives in [docs/PROTOCOL.md](docs/PROTOCOL.md), with machine-readable JSON Schemas published alongside the package (`cc-peer/schemas/*.schema.json`).
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install cc-peer
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or run the REST facade with no install:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx cc-peer
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { CcPeer } from "cc-peer";
|
|
38
|
+
|
|
39
|
+
const peer = await CcPeer.create({ name: "my-app" });
|
|
40
|
+
|
|
41
|
+
peer.on("message", (m) => console.log(`${m.fromName ?? m.from}: ${m.body}`));
|
|
42
|
+
peer.on("receipt", (r) => console.log(`status: ${r.status}`));
|
|
43
|
+
|
|
44
|
+
const sessions = await peer.roster();
|
|
45
|
+
const { msgId } = await peer.send(sessions[0]!.pid, "hello from my app");
|
|
46
|
+
|
|
47
|
+
await peer.subscribeIdle(sessions[0]!.pid);
|
|
48
|
+
peer.on("idle", (n) => console.log(`session ${n.state}`));
|
|
49
|
+
|
|
50
|
+
// …later
|
|
51
|
+
await peer.stop();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The REST facade (`npx cc-peer`) serves `GET /sessions`, `POST /messages`, `POST /idle-subscriptions`, `GET /events` (SSE), and a self-describing `GET /openapi.json` on loopback with a bearer token.
|
|
55
|
+
|
|
56
|
+
## Limitations
|
|
57
|
+
|
|
58
|
+
- **Same-process constraint**: receipts and idle notices only reach the process that owns the peer's listening socket (the protocol verifies return addresses via kernel peer-pids). Do not split `CcPeer` listening and sending across processes or differently-owned workers.
|
|
59
|
+
- **Single machine**: the local protocol is Unix-socket only. Writing to cloud sessions directly is blocked by design (device-attestation-signed events); bridged sessions reachable locally still work via their local mirror.
|
|
60
|
+
- **File transfers to Claude sessions** wait on an upstream feature flag (`tengu_send_file`) before Claude-side materialisation activates; peer-to-peer transfers work today.
|
|
61
|
+
- Verified against Claude Code 2.1.269; treat every Claude Code upgrade as a potential protocol change.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cc-peer.cjs
ADDED
package/dist/cc-peer.mjs
ADDED
package/docs/PROTOCOL.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# cc-peer protocol reference
|
|
2
|
+
|
|
3
|
+
This document is the implement-it-yourself reference for Claude Code's local cross-session peer messaging, reverse-engineered and live-verified against Claude Code 2.1.269 by the cc-peer project. Machine-readable companions ship with the package (`cc-peer/schemas/*.schema.json`, JSON Schema draft 2020-12, generated from the same Zod definitions the SDK uses).
|
|
4
|
+
|
|
5
|
+
Unofficial and unaffiliated with Anthropic; the protocol may change without notice between Claude Code releases.
|
|
6
|
+
|
|
7
|
+
# Claude Code — Cross-Session Messaging UDS Protocol
|
|
8
|
+
|
|
9
|
+
How `SendMessage` / `ListAgents` peer messaging actually travels between Claude Code processes: a per-session Unix domain socket with a file-backed bearer token, newline-delimited JSON framing, a permission-attestation consent model, and a receipted reverse channel — plus a cloud bridge leg for off-machine delivery. Reverse-engineered from the 2.1.269 binary and verified end to end with hand-rolled raw-socket clients (no harness tools): fresh Claude sessions received, held, and replied to injected messages; a standalone Python process registered as a named peer and exchanged messages, receipts, and subscriptions with real sessions.
|
|
10
|
+
|
|
11
|
+
The consent design parallels agent-comms (ExaDev's cross-harness mesh; its consent design parallels this protocol)' six-obligation room-token work: credential-gated transport, kernel peer-pid verification, permission-mode attestation, hold-for-review, and return-address verification.
|
|
12
|
+
|
|
13
|
+
## Transport and identity
|
|
14
|
+
|
|
15
|
+
- Each interactive session binds `/tmp/cc-socks/<pid>.sock` (Unix socket, same-uid peers only). The registry entry `~/.claude/sessions/<pid>.json` records `messagingSocketPath`, `sessionId`, `bridgeSessionId`, `peerProtocol`, `peerFeatures` (`notify_idle`, `reply_across_default_dirs`, `artifact_yield`), name, status, and `procStart`.
|
|
16
|
+
- On startup the session writes a key file `~/.claude/sessions/<pid>.<hash>.key` (mode 0600) where `<hash>` is `sha256` of the canonical socket path. Content: `{"peerToken":"<32 hex>","procStart":"...","pidDomain":"darwin"}`. A second in-process token, the `childToken`, is exported to subprocesses via `CLAUDE_CODE_MESSAGING_TOKEN` (with `CLAUDE_CODE_MESSAGING_SOCKET` and `CLAUDE_CODE_CHILD_SESSION=1`) — the inbox's startup banner documents the official injection recipe: `echo '{"type":"auth","token":"'$CLAUDE_CODE_MESSAGING_TOKEN'"}'; echo '{"type":"user",...}' | socat - UNIX-CONNECT:$CLAUDE_CODE_MESSAGING_SOCKET`.
|
|
17
|
+
- Sender-side vetting (`Pe` in the uds-client chunk): the target's key file is read, its owning pid checked alive with a matching proc-start token (recycled-pid defence), and optionally the connected peer's pid/uid read from the kernel. A live socket with no vouching key is refused on platforms where auth is mandatory; on macOS auth is optional, and an unrecognised token degrades to the unauthenticated path rather than being rejected.
|
|
18
|
+
- Address schemes (`Jf`): `uds:<path>`, `bridge:<id>`, `did:<x>` (reserved — parsed as raw passthrough, no local resolution in 2.1.269), bare `*.sock` paths, and Windows named pipes.
|
|
19
|
+
|
|
20
|
+
## Wire protocol
|
|
21
|
+
|
|
22
|
+
One connection, one write, then close (~150 ms linger on macOS). Two newline-terminated JSON lines: auth, then the frame. Nothing is ever acknowledged on the same connection — delivery surfaces in the receiver's conversation, and status travels back as separate receipt connections.
|
|
23
|
+
|
|
24
|
+
**Line 1, auth** — the target's own `peerToken` (peer-class) or `childToken` (child-class):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{"type":"auth","token":"<peerToken or childToken>"}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Line 2, user message** (`hnt`):
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"msgV": 1,
|
|
35
|
+
"msg_id": "<uuid v4>",
|
|
36
|
+
"type": "user",
|
|
37
|
+
"message": {"role": "user", "content": "<cross-session-message envelope, below>"},
|
|
38
|
+
"priority": "next",
|
|
39
|
+
"from": "uds:/tmp/cc-socks/<sender pid>.sock",
|
|
40
|
+
"file_attachments": [{"path": "...", "file_name": "...", "file_size": 0, "sha256": "...", "media_type": "..."}]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `"type":"user"` is load-bearing: any other type value is silently dropped after auth, with the connection left open and no error — a wrong type is indistinguishable from a delivery that never happened.
|
|
45
|
+
- `msgV` is exactly `1` in genuine traffic and not strictly validated (`2` and absent both deliver). `msg_id` is a `randomUUID()`. `priority` is `"next"` on the SendMessage path; `"later"` is accepted identically. A `session_id` field, when present, must match the receiver's session id or the frame is silently dropped (no hold, no receipt, no trace).
|
|
46
|
+
- Control frames share the connection shape with `"type":"control"` plus an `action` field — see Receipts and Idle subscriptions.
|
|
47
|
+
|
|
48
|
+
## The envelope
|
|
49
|
+
|
|
50
|
+
`message.content` must be the tag-wrapped envelope built by `uGe`/`zfe` and round-trip-checked by `HB`:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
<cross-session-message from="uds:..." from-session="..." hop-chain="..." from-name="..." from-mode="...">BODY</cross-session-message>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Attribute **order is canonical and load-bearing**: `from, from-session, hop-chain, from-name, from-mode`. The parser regex matches that sequence only; a wrong order or an invalid attribute value fails the parse, the round-trip rebuild fails, and the entire envelope is treated as an opaque unattested body (held, raw tags shown in the preview).
|
|
57
|
+
- Grammar: `from` charset `[A-Za-z0-9%:_/.\-]`, max 300; `from-session` `[A-Za-z0-9_-]{1,80}`; `hop-chain` comma-joined 24-hex ids, max 32 entries at the grammar level; `from-name` free-ish text with `"` `<` `>` stripped and lookalikes normalised, max 80; `from-mode` one of the permission-mode enum (`bypass`, `prompting`, ...). Body: literal text, one newline inside each tag; occurrences of the closing tag are escaped to `<\`.
|
|
58
|
+
|
|
59
|
+
## Consent: holds, attestation, and the self-sent verdict
|
|
60
|
+
|
|
61
|
+
An unattested envelope arriving at a session that bypasses permission prompts is **held for human approval** ("The sender did not attest its permission mode and this session bypasses prompts"), with a Deny/Deliver dialog; `crossSessionInbound: accept` bypasses the hold. The setting's default (unset) is mode-parity: auto-deliver only on bypass↔bypass or prompting↔prompting; unattested senders are held only while the receiver bypasses.
|
|
62
|
+
|
|
63
|
+
The self-sent verdict (`ye`) short-circuits the hold entirely. A message is self-sent when the connecting process's **ancestry includes the target pid**, or it presents the target's **childToken** with no contrary evidence (macOS walks `ps` ancestry; the childToken sits in every subprocess env, which is precisely the supported injection path). Verified live: an unattested frame with the session's own childToken delivered straight into the sending session's conversation mid-turn; a *foreign* childToken grants nothing — it falls to the ordinary hold. So child parity is strictly per-parent.
|
|
64
|
+
|
|
65
|
+
## Registering a standalone peer and name discovery
|
|
66
|
+
|
|
67
|
+
A standalone process becomes a first-class peer with three artifacts, all self-writable:
|
|
68
|
+
|
|
69
|
+
1. Bind `/tmp/cc-socks/<pid>.sock`.
|
|
70
|
+
2. Write the key file with a self-generated `peerToken`.
|
|
71
|
+
3. Write the registry entry `<pid>.json` with `messagingSocketPath`, `name`, `status`, `peerFeatures`, and a correct `procStart`.
|
|
72
|
+
|
|
73
|
+
**`procStart` must byte-match `LC_ALL=C TZ=UTC ps -o lstart= -p <pid>` output** — the exact command Claude's own generators use, compared as a plain string. Two traps: macOS `ps` formats `lstart` per the locale (bare `ps` under en_GB emits day-before-month; `LC_ALL=C` emits ctime order), and the value is UTC, not local. A mismatch classifies the pid as `recycled` and the roster silently skips the entry. Store the forced-locale/UTC command's output verbatim.
|
|
74
|
+
|
|
75
|
+
The roster builder (`listLivePeerSessions`) reads all `~/.claude/sessions/<pid>.json` files — no daemon involvement; the filesystem is authoritative — and includes an entry when it has a `sock`, is not the caller's own, is not spare/parked, its socket accepts a live connect probe, and its pid is `present` (alive with matching `procStart`; `gone` entries are swept, `recycled` skipped). `nameSource` and `status` are whitelisted on read; out-of-list values parse harmlessly to undefined.
|
|
76
|
+
|
|
77
|
+
Verified chain: a Python peer registered this way appears in `ListAgents` within seconds and receives native `SendMessage` by bare name (`from-name` resolves from the sender's own registry entry).
|
|
78
|
+
|
|
79
|
+
## Receipts and status
|
|
80
|
+
|
|
81
|
+
`peer_message_status` is pushed from receiver to sender over a fresh connection to the sender's socket, authenticated with the sender's own peerToken:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{"type":"control","action":"peer_message_status","status":"held","reason":"<human-readable>","from":"uds:/tmp/cc-socks/<receiver>.sock","orig_msg_id":"<the send's msg_id>","msgV":1,"msg_id":"<receipt's own id>"}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
All statuses verified on the wire:
|
|
88
|
+
|
|
89
|
+
| Status | When | Extra fields |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `held` | unattested message entered the approval dialog | — |
|
|
92
|
+
| `delivered` | hold approved and released | — |
|
|
93
|
+
| `denied` | hold denied | — |
|
|
94
|
+
| `expired` | hold unapproved past TTL (~25 min; pending holds expire together) | `status_detail:"refused"` when the receiver refuses inbound |
|
|
95
|
+
| `dropped` | rejected at inbox admission | `drop_reason`, `dropped_msg_ids` |
|
|
96
|
+
|
|
97
|
+
Clean fire-and-forget delivery pushes no receipt — receipts exist for holds and failures.
|
|
98
|
+
|
|
99
|
+
Drop reasons (drop taxonomy): `duplicate` (same sender + same body hash within `dedupWindowMs` 30 s — **not** msg_id; identical msg_id with different bodies both deliver), `rate-limited` (token bucket below), `hop-loop` and `hop-runaway` (hop-chain guards below), `queue-full` (undelivered-queue cap). Verified live: `duplicate`, `rate-limited` (35-message burst against the 30-token bucket), `hop-runaway` (29-entry chain), `hop-loop` (12× target-own token). `queue-full` was attempted with 55 admitted messages queued behind a hold modal and did not fire — the effective cap is dynamically raised above the 50 code default (`tengu_harbor_kite_limits`, zod range 10-5000); it is the one code-verified variant, with a receipt frame identical to its captured siblings.
|
|
100
|
+
|
|
101
|
+
## Idle subscriptions
|
|
102
|
+
|
|
103
|
+
Subscribe: `{"type":"control","action":"notify_when_idle","from":"uds:...","from_mode":"bypass","msgV":1,"msg_id":"<uuid>"}`. The notice returns correlated by `orig_msg_id` = the subscription's `msg_id`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{"type":"control","action":"peer_idle_notice","orig_msg_id":"...","state":"idle","finished_at":1789216922033,"detail":"<preview of the target's last reply>","from":"uds:...","from_mode":"bypass","msgV":1,"msg_id":"..."}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`state` is `idle` (verified: fires immediately if the target is already idle) or `exited` (verified: fires on session shutdown, including a kill). `finished_at` is epoch-ms of the target's last turn end.
|
|
110
|
+
|
|
111
|
+
## Guard rails and admission semantics
|
|
112
|
+
|
|
113
|
+
Peer-guard defaults (`tengu_harbor_kite_limits`-overridable): `bucketCapacity:30, refillPerSecond:0.5, dedupWindowMs:30000, maxSelfHops:10, maxChainLength:28, maxTrackedSenders:256`.
|
|
114
|
+
|
|
115
|
+
**Return-address verification** — the load-bearing reverse-channel rule: the receiver records the kernel-reported peer pid of each inbound connection and pushes receipts/notices only if the claimed `from` socket is owned by that same live process. Messages from a throwaway client claiming another peer's address deliver fine but receive nothing back ("unvettable reply target"). A standalone peer must send from the process that binds its socket. (Trap: macOS Python launchers — Homebrew and Xcode `Python.app` shims — fork before exec; the surviving process owns the socket and must be the sender.)
|
|
116
|
+
|
|
117
|
+
**Hop tokens** — `ownUdsHopToken = HMAC-SHA256(key = randomBytes(32) at process init, msg = "uds:<canonical socket path>").hex().slice(0,24)`: per-boot ephemeral, not externally computable. The loop-detection self-token set also covers the bridge address and a bridge-identity id. The loop guard fires at ≥ `maxSelfHops` (10) occurrences of a self-token — a single occurrence is harmless and delivers. Outbound replies to peer-origin messages stamp `i_e(own address)` into the chain, so a target's token is **mintable** by asking it to reply once and reading the chain it emits; a 12×-token chain then triggers `hop-loop`. Chains over 32 entries fail envelope parsing (hold as unattested); 29-32-entry chains parse but trip `hop-runaway` at the > 28 guard.
|
|
118
|
+
|
|
119
|
+
Also enforced: a max line cap (`message_too_large`), symlink refusal on reply targets, and stale-socket refusal keyed to pid liveness plus proc-start tokens.
|
|
120
|
+
|
|
121
|
+
## artifact_yield (`yield_artifact_replies` family)
|
|
122
|
+
|
|
123
|
+
Same-conversation primitive: one live process of a conversation asks another to hand over in-flight artifact-reply generation.
|
|
124
|
+
|
|
125
|
+
- Request: `{action:"yield_artifact_replies", from, msg_id, session_id, slugs[<=16], reason:"resume"|"claim" (default), sent_at (epoch ms, ~4 s freshness window), claimed_at?, requester:{cwd?,tmux?}}`
|
|
126
|
+
- Answer: `{action:"artifact_replies_yielded", orig_msg_id, yielded?, not_held?, refused?}`
|
|
127
|
+
- Hand-back: `{action:"unyield_artifact_replies", orig_msg_id, slugs, stopped?}`
|
|
128
|
+
|
|
129
|
+
Admission (verified live, both directions): the target looks up the requester's registry record by socket and requires its `sessionId` to equal the target's own conversation id, plus pid match. With a random registry `sessionId` the request is refused silently; with the target's `sessionId` written into the registry, the target admitted the request and answered `artifact_replies_yielded {yielded:[], not_held:["probe-artifact"]}`. Trust boundary: the conversation gate reads the same-user-writable registry — it gates capability between processes, not identity against the local user.
|
|
130
|
+
|
|
131
|
+
## File transfer (`file_attachments`)
|
|
132
|
+
|
|
133
|
+
Sender stages each file into `~/.claude/file-transfers/<sha8>-<uuid8>-<name>` (0600) and attaches the descriptor array to the frame. Caps: 30 MiB per file, 16 per message, 1-day spool GC. Receiver validates each descriptor (absolute path, parent must be the spool, regular file, size, sha256 integrity), copies into `~/.claude/uploads/<receiving-session-id>/`, deletes the staged copy when spools are shared, and prepends `@"<uploads path>"` mentions plus `[SendFile: ... was not delivered — <reason>]` failure notes to the delivered body.
|
|
134
|
+
|
|
135
|
+
Staging and the descriptor-carrying frame replicate exactly, but receive-side materialisation never executes on this account: the `tengu_send_file` flag is absent from the served Statsig evaluations (never served on, not merely cached-off), proven by a deliberately sha-mismatched descriptor producing no inline failure note and no uploads directory. The gate is a server rollout decision; everything up to it is documented and the send-side gate (`A6e()`) is explicit in code.
|
|
136
|
+
|
|
137
|
+
## Cloud and bridge routing
|
|
138
|
+
|
|
139
|
+
Local UDS is one leg. Every signed-in session carries a `bridgeSessionId` in its registry (universally present), mirrored at `https://claude.ai/code/<bridgeSessionId>`. Roster candidates without a `sock` (cloud-session / bridge-session kinds) route over the first-party Sessions API at `https://api.anthropic.com`:
|
|
140
|
+
|
|
141
|
+
- `GET /v1/code/sessions`, `GET /v1/code/sessions/<id>` — roster and detail
|
|
142
|
+
- `POST /v1/code/sessions/<id>/events` — signed, batched turn events (`anthropic/ccr-turn-event-uuid`, `anthropic/ccr-turn-linked-event-uuids`, `traceparent` headers)
|
|
143
|
+
- `GET .../events` and `.../events/stream` — sequenced, redialling stream with liveness timeouts and service-clock drift checks
|
|
144
|
+
- `mark_read`, `archive`, title, `bridge`, `device` (attestation binding), `teleport-events`, `move-to-cloud`, `client/presence`, `synced_file/*`, self-hosted runner/worker endpoints
|
|
145
|
+
- Auth: OAuth bearer plus trusted-device headers; `isolatePeerMachines` gates cross-machine sends
|
|
146
|
+
|
|
147
|
+
Event signing: the payload is canonicalised with JCS (`claude-code-jcs@1`), signed with an external device key into an `anthropic.ccr.client_event.v1` attestation, and bound via `anthropic.ccr.create_session_bind.v1` / `session_bind.v1` messages carrying a `boundDeviceUuid`. Unattested events classify `bound_unattested` server-side. The device key is not extractable by design — the replication boundary for standalone cloud writes.
|
|
148
|
+
|
|
149
|
+
Delivery to cloud targets is best-effort: sessions report `acceptsPeerMessages`, and senders surface "accepted by the server ... but delivery is not confirmed" when unreported. Verified live: a `SendMessage` to a `bridge:<bridgeSessionId>` address was server-accepted with exactly that caveat and arrived in the target's local transcript (the bridge id maps back to the conversation via cloud ingest or local-sock mirroring; the code supports both legs).
|
|
150
|
+
|
|
151
|
+
## Adjacent: the daemon
|
|
152
|
+
|
|
153
|
+
`~/.claude/daemon` supervises background/scheduled agents, not interactive sessions: `roster.json` (v5) tracks workers (pid, procStart, sessionId, rendezvous socket under `/tmp/cc-daemon-501/<hash>/rv/`, pty socket), gated by `control.key` with uid-checked control connections.
|
|
154
|
+
|
|
155
|
+
## Reproduction
|
|
156
|
+
|
|
157
|
+
Minimal sender (auth + one user frame, single write):
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
import socket, json, uuid, time
|
|
161
|
+
TOKEN = "<target peerToken from ~/.claude/sessions/<pid>.<hash>.key>"
|
|
162
|
+
SOCK, FROM = "/tmp/cc-socks/<target>.sock", "uds:/tmp/cc-socks/<sender>.sock"
|
|
163
|
+
wrapper = f'<cross-session-message from="{FROM}" from-name="<name>" from-mode="bypass">\n<body>\n</cross-session-message>'
|
|
164
|
+
frame = {"msgV": 1, "msg_id": str(uuid.uuid4()), "type": "user",
|
|
165
|
+
"message": {"role": "user", "content": wrapper},
|
|
166
|
+
"priority": "next", "from": FROM}
|
|
167
|
+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.settimeout(5)
|
|
168
|
+
s.connect(SOCK)
|
|
169
|
+
s.sendall((json.dumps({"type": "auth", "token": TOKEN}) + "\n" + json.dumps(frame) + "\n").encode())
|
|
170
|
+
time.sleep(0.2); s.close()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
A receiving peer is the mirror: bind the socket, write key file and registry (procStart from `LC_ALL=C TZ=UTC ps -o lstart=` verbatim), accept connections, verify the auth line against the peerToken, parse frames, and — for receipts and notices to arrive — send only from the socket-owning process.
|
|
174
|
+
|
|
175
|
+
## Verification status
|
|
176
|
+
|
|
177
|
+
| Feature | Status |
|
|
178
|
+
|---|---|
|
|
179
|
+
| Transport, auth (peer/child/unauthenticated), user frames, envelope grammar | verified live, both directions |
|
|
180
|
+
| Holds, attestation, self-sent verdict, `crossSessionInbound` parity | verified live |
|
|
181
|
+
| Peer registration, roster admission, name discovery | verified live (standalone peer in `ListAgents`, named `SendMessage`) |
|
|
182
|
+
| Receipts: held / delivered / denied / expired / dropped{duplicate, rate-limited, hop-loop, hop-runaway} | verified live |
|
|
183
|
+
| `queue-full` | code-verified; trigger attempted (55 queued), effective cap dynamically raised |
|
|
184
|
+
| Idle subscriptions (`idle`, `exited`) | verified live |
|
|
185
|
+
| artifact_yield admission + answer | verified live (refused and admitted paths); populated handover not exercised |
|
|
186
|
+
| File transfer | staging + wire replicated; receive path behind a never-served server flag (evidenced) |
|
|
187
|
+
| Cloud/bridge routing | `bridge:` addressing verified live; sessions-API surface and event-signer envelope extracted; device key not extractable by design |
|
|
188
|
+
|
|
189
|
+
## Provenance
|
|
190
|
+
|
|
191
|
+
Recovered by live reverse-engineering of the 2.1.269 binary and verified with hand-rolled raw-socket clients against fresh Claude Code sessions and a registered standalone peer: injected messages delivered and answered, receipts captured for every status, drop reasons triggered on demand, idle notices observed in both states, and the same-conversation yield admission demonstrated in both directions. Boundaries (device-attestation signing, the upstream file-transfer flag, the dynamically raised queue cap) are stated where they apply.
|
|
192
|
+
|
|
193
|
+
The accompanying JSON Schemas are generated from the Zod definitions in `src/schemas/` — the same single source of truth the SDK runtime uses.
|
package/package.json
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-peer",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Communicate with local Claude Code instances over their native cross-session peer messaging: send and receive messages, register as a named discoverable peer, receipts, idle subscriptions, and a REST facade via `npx cc-peer`.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ExaDev/cc-peer.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ExaDev/cc-peer#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ExaDev/cc-peer/issues"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
20
|
+
"packageManager": "pnpm@10.33.0",
|
|
21
|
+
"bin": {
|
|
22
|
+
"cc-peer": "./dist/bin/cc-peer.mjs"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/cc-peer.d.mts",
|
|
28
|
+
"default": "./dist/cc-peer.mjs"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/cc-peer.d.cts",
|
|
32
|
+
"default": "./dist/cc-peer.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"./schemas/*.schema.json": "./schemas/*.schema.json",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/cc-peer.cjs",
|
|
39
|
+
"types": "./dist/cc-peer.d.mts",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"schemas",
|
|
43
|
+
"docs"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"provenance": true,
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "turbo run _build",
|
|
51
|
+
"_build": "tsdown && tsx scripts/generate-json-schema.ts",
|
|
52
|
+
"lint": "turbo run _lint",
|
|
53
|
+
"_lint": "eslint . --cache",
|
|
54
|
+
"typecheck": "turbo run _typecheck",
|
|
55
|
+
"_typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"test:mutation": "stryker run",
|
|
59
|
+
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && publint && attw --pack",
|
|
60
|
+
"prepare": "husky"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"zod": "4.5.4"
|
|
64
|
+
},
|
|
65
|
+
"pnpm": {
|
|
66
|
+
"onlyBuiltDependencies": [
|
|
67
|
+
"@anthropic-ai/claude-code",
|
|
68
|
+
"esbuild",
|
|
69
|
+
"protobufjs",
|
|
70
|
+
"koffi"
|
|
71
|
+
],
|
|
72
|
+
"overrides": {
|
|
73
|
+
"eslint-plugin-jsdoc": "64.3.6",
|
|
74
|
+
"conventional-changelog-writer": "9.2.1"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@anthropic-ai/claude-code": "2.1.261",
|
|
79
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
80
|
+
"@commitlint/config-conventional": "21.2.2",
|
|
81
|
+
"@commitlint/types": "21.2.0",
|
|
82
|
+
"@exadev/eslint-config": "2.11.0",
|
|
83
|
+
"@semantic-release/changelog": "7.0.0",
|
|
84
|
+
"@semantic-release/commit-analyzer": "13.0.1",
|
|
85
|
+
"@semantic-release/exec": "7.1.0",
|
|
86
|
+
"@semantic-release/git": "11.0.1",
|
|
87
|
+
"@semantic-release/github": "12.0.9",
|
|
88
|
+
"@semantic-release/npm": "13.1.5",
|
|
89
|
+
"@semantic-release/release-notes-generator": "14.1.1",
|
|
90
|
+
"@stryker-mutator/core": "10.0.0",
|
|
91
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
92
|
+
"@types/node": "26.4.1",
|
|
93
|
+
"commitlint": "21.2.2",
|
|
94
|
+
"eslint": "10.10.0",
|
|
95
|
+
"eslint-config-prettier": "10.1.8",
|
|
96
|
+
"eslint-plugin-prettier": "5.5.6",
|
|
97
|
+
"globals": "17.12.0",
|
|
98
|
+
"husky": "9.1.7",
|
|
99
|
+
"jiti": "2.7.0",
|
|
100
|
+
"lint-staged": "17.5.0",
|
|
101
|
+
"prettier": "3.9.6",
|
|
102
|
+
"publint": "0.3.24",
|
|
103
|
+
"semantic-release": "25.0.9",
|
|
104
|
+
"tsdown": "0.23.0",
|
|
105
|
+
"tsx": "4.23.13",
|
|
106
|
+
"turbo": "2.10.12",
|
|
107
|
+
"typescript": "6.0.3",
|
|
108
|
+
"vitest": "5.0.0"
|
|
109
|
+
}
|
|
5
110
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "artifact_replies_yielded"
|
|
12
|
+
},
|
|
13
|
+
"orig_msg_id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"maxLength": 128
|
|
16
|
+
},
|
|
17
|
+
"yielded": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"not_held": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"refused": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"msgV": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": -9007199254740991,
|
|
29
|
+
"maximum": 9007199254740991
|
|
30
|
+
},
|
|
31
|
+
"msg_id": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
},
|
|
35
|
+
"from": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"required": [
|
|
40
|
+
"type",
|
|
41
|
+
"action",
|
|
42
|
+
"orig_msg_id",
|
|
43
|
+
"msgV",
|
|
44
|
+
"msg_id"
|
|
45
|
+
],
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "auth"
|
|
8
|
+
},
|
|
9
|
+
"token": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"pattern": "^[0-9a-f]{32}$"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"type",
|
|
16
|
+
"token"
|
|
17
|
+
],
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"from": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"pattern": "^[A-Za-z0-9%:_/.\\\\-]{1,300}$"
|
|
8
|
+
},
|
|
9
|
+
"fromSession": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"pattern": "^[A-Za-z0-9_-]{1,80}$"
|
|
12
|
+
},
|
|
13
|
+
"hopChain": {
|
|
14
|
+
"maxItems": 32,
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"pattern": "^[0-9a-f]{24}$"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"fromName": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"minLength": 1,
|
|
24
|
+
"maxLength": 80
|
|
25
|
+
},
|
|
26
|
+
"fromMode": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": [
|
|
29
|
+
"bypass",
|
|
30
|
+
"prompting"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": [
|
|
35
|
+
"from"
|
|
36
|
+
],
|
|
37
|
+
"additionalProperties": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "notify_when_idle"
|
|
12
|
+
},
|
|
13
|
+
"from": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[A-Za-z0-9%:_/.\\\\-]{1,300}$"
|
|
16
|
+
},
|
|
17
|
+
"from_mode": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": [
|
|
20
|
+
"bypass",
|
|
21
|
+
"prompting"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"msgV": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"minimum": -9007199254740991,
|
|
27
|
+
"maximum": 9007199254740991
|
|
28
|
+
},
|
|
29
|
+
"msg_id": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": [
|
|
35
|
+
"type",
|
|
36
|
+
"action",
|
|
37
|
+
"from",
|
|
38
|
+
"msgV",
|
|
39
|
+
"msg_id"
|
|
40
|
+
],
|
|
41
|
+
"additionalProperties": false
|
|
42
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "peer_idle_notice"
|
|
12
|
+
},
|
|
13
|
+
"orig_msg_id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1
|
|
16
|
+
},
|
|
17
|
+
"state": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": [
|
|
20
|
+
"idle",
|
|
21
|
+
"exited"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"finished_at": {
|
|
25
|
+
"type": "number"
|
|
26
|
+
},
|
|
27
|
+
"detail": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"from": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^[A-Za-z0-9%:_/.\\\\-]{1,300}$"
|
|
33
|
+
},
|
|
34
|
+
"from_mode": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"enum": [
|
|
37
|
+
"bypass",
|
|
38
|
+
"prompting"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"msgV": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"minimum": -9007199254740991,
|
|
44
|
+
"maximum": 9007199254740991
|
|
45
|
+
},
|
|
46
|
+
"msg_id": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"type",
|
|
53
|
+
"action",
|
|
54
|
+
"orig_msg_id",
|
|
55
|
+
"state",
|
|
56
|
+
"finished_at",
|
|
57
|
+
"from",
|
|
58
|
+
"msgV",
|
|
59
|
+
"msg_id"
|
|
60
|
+
],
|
|
61
|
+
"additionalProperties": false
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"peerToken": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"pattern": "^[0-9a-f]{32}$"
|
|
8
|
+
},
|
|
9
|
+
"procStart": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"minLength": 1
|
|
12
|
+
},
|
|
13
|
+
"pidDomain": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": [
|
|
19
|
+
"peerToken",
|
|
20
|
+
"procStart",
|
|
21
|
+
"pidDomain"
|
|
22
|
+
],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "peer_message_status"
|
|
12
|
+
},
|
|
13
|
+
"status": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"held",
|
|
17
|
+
"delivered",
|
|
18
|
+
"denied",
|
|
19
|
+
"expired",
|
|
20
|
+
"dropped"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"reason": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"from": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "^[A-Za-z0-9%:_/.\\\\-]{1,300}$"
|
|
29
|
+
},
|
|
30
|
+
"orig_msg_id": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"minLength": 1
|
|
33
|
+
},
|
|
34
|
+
"status_detail": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
"drop_reason": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": [
|
|
40
|
+
"rate-limited",
|
|
41
|
+
"duplicate",
|
|
42
|
+
"hop-loop",
|
|
43
|
+
"hop-runaway",
|
|
44
|
+
"queue-full"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"dropped_msg_ids": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"msgV": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": -9007199254740991,
|
|
56
|
+
"maximum": 9007199254740991
|
|
57
|
+
},
|
|
58
|
+
"msg_id": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"minLength": 1
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": [
|
|
64
|
+
"type",
|
|
65
|
+
"action",
|
|
66
|
+
"status",
|
|
67
|
+
"reason",
|
|
68
|
+
"from",
|
|
69
|
+
"orig_msg_id",
|
|
70
|
+
"msgV",
|
|
71
|
+
"msg_id"
|
|
72
|
+
],
|
|
73
|
+
"additionalProperties": false
|
|
74
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"pid": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"exclusiveMinimum": 0,
|
|
8
|
+
"maximum": 9007199254740991
|
|
9
|
+
},
|
|
10
|
+
"sessionId": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1
|
|
13
|
+
},
|
|
14
|
+
"cwd": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"startedAt": {
|
|
18
|
+
"type": "integer",
|
|
19
|
+
"minimum": 0,
|
|
20
|
+
"maximum": 9007199254740991
|
|
21
|
+
},
|
|
22
|
+
"procStart": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 1
|
|
25
|
+
},
|
|
26
|
+
"version": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1
|
|
29
|
+
},
|
|
30
|
+
"peerProtocol": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"minimum": -9007199254740991,
|
|
33
|
+
"maximum": 9007199254740991
|
|
34
|
+
},
|
|
35
|
+
"peerFeatures": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": [
|
|
40
|
+
"notify_idle",
|
|
41
|
+
"reply_across_default_dirs",
|
|
42
|
+
"artifact_yield"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"kind": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"enum": [
|
|
49
|
+
"interactive",
|
|
50
|
+
"bg",
|
|
51
|
+
"daemon",
|
|
52
|
+
"daemon-worker"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"entrypoint": {
|
|
56
|
+
"type": "string"
|
|
57
|
+
},
|
|
58
|
+
"pidDomain": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"minLength": 1
|
|
61
|
+
},
|
|
62
|
+
"messagingSocketPath": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"minLength": 1
|
|
65
|
+
},
|
|
66
|
+
"name": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
"nameSource": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": [
|
|
72
|
+
"user",
|
|
73
|
+
"peer",
|
|
74
|
+
"derived",
|
|
75
|
+
"collision",
|
|
76
|
+
"auto",
|
|
77
|
+
"hook"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"nameSince": {
|
|
81
|
+
"type": "integer",
|
|
82
|
+
"minimum": 0,
|
|
83
|
+
"maximum": 9007199254740991
|
|
84
|
+
},
|
|
85
|
+
"updatedAt": {
|
|
86
|
+
"type": "integer",
|
|
87
|
+
"minimum": 0,
|
|
88
|
+
"maximum": 9007199254740991
|
|
89
|
+
},
|
|
90
|
+
"status": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"busy",
|
|
94
|
+
"shell",
|
|
95
|
+
"idle",
|
|
96
|
+
"waiting"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"statusUpdatedAt": {
|
|
100
|
+
"type": "integer",
|
|
101
|
+
"minimum": 0,
|
|
102
|
+
"maximum": 9007199254740991
|
|
103
|
+
},
|
|
104
|
+
"bridgeSessionId": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"required": [
|
|
109
|
+
"pid",
|
|
110
|
+
"sessionId",
|
|
111
|
+
"cwd",
|
|
112
|
+
"startedAt",
|
|
113
|
+
"procStart",
|
|
114
|
+
"version",
|
|
115
|
+
"peerProtocol",
|
|
116
|
+
"peerFeatures",
|
|
117
|
+
"kind",
|
|
118
|
+
"entrypoint",
|
|
119
|
+
"pidDomain",
|
|
120
|
+
"messagingSocketPath",
|
|
121
|
+
"updatedAt"
|
|
122
|
+
],
|
|
123
|
+
"additionalProperties": false
|
|
124
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "unyield_artifact_replies"
|
|
12
|
+
},
|
|
13
|
+
"orig_msg_id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"maxLength": 128
|
|
16
|
+
},
|
|
17
|
+
"slugs": {
|
|
18
|
+
"maxItems": 16,
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"maxLength": 128
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"stopped": {
|
|
26
|
+
"type": "boolean"
|
|
27
|
+
},
|
|
28
|
+
"msgV": {
|
|
29
|
+
"type": "integer",
|
|
30
|
+
"minimum": -9007199254740991,
|
|
31
|
+
"maximum": 9007199254740991
|
|
32
|
+
},
|
|
33
|
+
"msg_id": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1
|
|
36
|
+
},
|
|
37
|
+
"from": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": [
|
|
42
|
+
"type",
|
|
43
|
+
"action",
|
|
44
|
+
"orig_msg_id",
|
|
45
|
+
"slugs",
|
|
46
|
+
"msgV",
|
|
47
|
+
"msg_id"
|
|
48
|
+
],
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"msgV": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"minimum": -9007199254740991,
|
|
8
|
+
"maximum": 9007199254740991
|
|
9
|
+
},
|
|
10
|
+
"msg_id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1
|
|
13
|
+
},
|
|
14
|
+
"type": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"const": "user"
|
|
17
|
+
},
|
|
18
|
+
"message": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"role": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"const": "user"
|
|
24
|
+
},
|
|
25
|
+
"content": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": [
|
|
30
|
+
"role",
|
|
31
|
+
"content"
|
|
32
|
+
],
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
},
|
|
35
|
+
"priority": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"enum": [
|
|
38
|
+
"next",
|
|
39
|
+
"later"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"from": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"pattern": "^[A-Za-z0-9%:_/.\\\\-]{1,300}$"
|
|
45
|
+
},
|
|
46
|
+
"session_id": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"file_attachments": {
|
|
50
|
+
"maxItems": 16,
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"properties": {
|
|
55
|
+
"path": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"minLength": 1
|
|
58
|
+
},
|
|
59
|
+
"file_name": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"minLength": 1
|
|
62
|
+
},
|
|
63
|
+
"file_size": {
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"minimum": 0,
|
|
66
|
+
"maximum": 9007199254740991
|
|
67
|
+
},
|
|
68
|
+
"sha256": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
71
|
+
},
|
|
72
|
+
"media_type": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": [
|
|
77
|
+
"path",
|
|
78
|
+
"file_name",
|
|
79
|
+
"file_size",
|
|
80
|
+
"sha256"
|
|
81
|
+
],
|
|
82
|
+
"additionalProperties": false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"required": [
|
|
87
|
+
"msgV",
|
|
88
|
+
"msg_id",
|
|
89
|
+
"type",
|
|
90
|
+
"message",
|
|
91
|
+
"priority",
|
|
92
|
+
"from"
|
|
93
|
+
],
|
|
94
|
+
"additionalProperties": false
|
|
95
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "control"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "yield_artifact_replies"
|
|
12
|
+
},
|
|
13
|
+
"from": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"maxLength": 512
|
|
16
|
+
},
|
|
17
|
+
"msg_id": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"minLength": 1,
|
|
20
|
+
"maxLength": 128
|
|
21
|
+
},
|
|
22
|
+
"session_id": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"maxLength": 512
|
|
25
|
+
},
|
|
26
|
+
"slugs": {
|
|
27
|
+
"maxItems": 16,
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"maxLength": 128
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"reason": {
|
|
35
|
+
"default": "claim",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"enum": [
|
|
38
|
+
"resume",
|
|
39
|
+
"claim"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"sent_at": {
|
|
43
|
+
"type": "number"
|
|
44
|
+
},
|
|
45
|
+
"claimed_at": {
|
|
46
|
+
"type": "number"
|
|
47
|
+
},
|
|
48
|
+
"requester": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"cwd": {
|
|
52
|
+
"type": "string"
|
|
53
|
+
},
|
|
54
|
+
"tmux": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
},
|
|
60
|
+
"msgV": {
|
|
61
|
+
"type": "integer",
|
|
62
|
+
"minimum": -9007199254740991,
|
|
63
|
+
"maximum": 9007199254740991
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"required": [
|
|
67
|
+
"type",
|
|
68
|
+
"action",
|
|
69
|
+
"from",
|
|
70
|
+
"msg_id",
|
|
71
|
+
"session_id",
|
|
72
|
+
"slugs",
|
|
73
|
+
"reason",
|
|
74
|
+
"sent_at",
|
|
75
|
+
"msgV"
|
|
76
|
+
],
|
|
77
|
+
"additionalProperties": false
|
|
78
|
+
}
|