@scp-dev/broker 0.0.0-stage → 0.1.1
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 +201 -0
- package/README.md +38 -2
- package/dist/index.d.ts +422 -0
- package/dist/index.js +2514 -0
- package/dist/testing.d.ts +30 -0
- package/dist/testing.js +131 -0
- package/package.json +48 -4
- package/src/bridge.ts +642 -0
- package/src/client.ts +336 -0
- package/src/index.ts +6 -0
- package/src/judge-gate.ts +552 -0
- package/src/lock.ts +113 -0
- package/src/ndjson.ts +95 -0
- package/src/paths.ts +132 -0
- package/src/server.ts +1124 -0
- package/src/socket.ts +57 -0
- package/src/sqlite.ts +90 -0
- package/src/testing.ts +168 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @scp-dev/broker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The SCP local broker: one daemon per user per machine, serving JSON-RPC on a unix socket, with its client and the bridge to an account hub.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @scp-dev/broker
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { RelayClient, relayDir, startBroker } from "@scp-dev/broker"
|
|
13
|
+
|
|
14
|
+
const broker = await startBroker({ dir: relayDir() })
|
|
15
|
+
|
|
16
|
+
const orch = await RelayClient.connect(broker.socket)
|
|
17
|
+
await orch.hello({ claim: { handle: "orch", harness: "claude-code" } })
|
|
18
|
+
const parser = await RelayClient.connect(broker.socket)
|
|
19
|
+
await parser.hello({ claim: { handle: "parser", harness: "codex" } })
|
|
20
|
+
|
|
21
|
+
await orch.request("send", { to: "parser", body: "Also cover the empty-input case." })
|
|
22
|
+
const { messages } = await parser.request<{ messages: { body: { text: string } }[] }>("inbox", { wait_s: 5 })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Clients that should start the daemon on demand use `connectBroker(dir, start)`.
|
|
26
|
+
|
|
27
|
+
## Provides
|
|
28
|
+
|
|
29
|
+
- `startBroker`: the daemon, with a single-instance lock, the policy file and an idle exit.
|
|
30
|
+
- `RelayClient` and `connectBroker`: the socket client every CLI verb, hook and MCP tool uses.
|
|
31
|
+
- The bridge to an account hub, opened only when configured.
|
|
32
|
+
- The judge gate at configured boundaries.
|
|
33
|
+
- Path helpers for `~/.local/state/scp` and `~/.config/scp`.
|
|
34
|
+
|
|
35
|
+
Implements [Transport and framing](https://scp.dev/spec#5-transport-and-framing), [Federation](https://scp.dev/spec#14-federation-and-cross-account) and the [wire methods](https://scp.dev/spec#appendix-a-wire-methods-local-broker). See the [root README](https://github.com/scphq/scp#readme).
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
[Apache-2.0](LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import * as _scp_dev_core from '@scp-dev/core';
|
|
2
|
+
import { ScopePreset, Claim, EndpointView, ScopeView, ScopeChange, Envelope, StopWaitResult, PeerView, WhyResult, SendParams, SendResult, ActivityRow, XListed, Selector, Sql, JudgeModelConfig, JudgeClient, JudgePolicy, Verdict } from '@scp-dev/core';
|
|
3
|
+
import { FederatedCore } from '@scp-dev/hub';
|
|
4
|
+
|
|
5
|
+
interface Hello {
|
|
6
|
+
claim?: Claim;
|
|
7
|
+
resume?: string;
|
|
8
|
+
/** the human inbox me/; `tty` says whether a terminal is attached, and is recorded on what it sends */
|
|
9
|
+
human?: boolean;
|
|
10
|
+
tty?: boolean;
|
|
11
|
+
/** a hook's connection: it claims like an agent but never counts as connected, and posts only event and status */
|
|
12
|
+
transient?: boolean;
|
|
13
|
+
/** a one-shot command: the broker announces no presence change when it connects or goes */
|
|
14
|
+
brief?: boolean;
|
|
15
|
+
/** no identity: list, status, watch, log and policy only */
|
|
16
|
+
observe?: boolean;
|
|
17
|
+
/** an observer's process lineage: when it lands on an endpoint, the observer gets that endpoint's view and may narrow */
|
|
18
|
+
lineage?: string[];
|
|
19
|
+
/** an observer's lineage up to its nearest harness process: when it lands on an agent endpoint, the connection acts as it with lineage auth */
|
|
20
|
+
self?: string[];
|
|
21
|
+
/** a spawn-env grant from a shell with no name: the hello binds only to the endpoint that spent it, or one below it */
|
|
22
|
+
grant?: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
}
|
|
25
|
+
interface BrokerInfo {
|
|
26
|
+
socket: string;
|
|
27
|
+
dir: string;
|
|
28
|
+
pid: number;
|
|
29
|
+
policy: {
|
|
30
|
+
file: string;
|
|
31
|
+
state: "open" | "loaded" | "error";
|
|
32
|
+
error?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
interface HelloResult {
|
|
36
|
+
endpoint: EndpointView | null;
|
|
37
|
+
token?: string;
|
|
38
|
+
/** the broker checked the hello's grant against the endpoint it bound */
|
|
39
|
+
grant_bound?: boolean;
|
|
40
|
+
/** a hook's subagent claim the broker honoured, and whether it made the endpoint */
|
|
41
|
+
subagent?: true;
|
|
42
|
+
created?: true;
|
|
43
|
+
capabilities: string[];
|
|
44
|
+
preamble: string;
|
|
45
|
+
broker: BrokerInfo;
|
|
46
|
+
}
|
|
47
|
+
interface GrantParams {
|
|
48
|
+
name?: string;
|
|
49
|
+
labels?: Record<string, string>;
|
|
50
|
+
title?: string;
|
|
51
|
+
under?: string;
|
|
52
|
+
scope?: ScopePreset;
|
|
53
|
+
in?: string[];
|
|
54
|
+
out?: string[];
|
|
55
|
+
allow?: string[];
|
|
56
|
+
groups?: string[];
|
|
57
|
+
ttl_s?: number;
|
|
58
|
+
/** a full process key: a claim from that process's tree with no grant of its own takes this grant (`swarm spawn` binds its own) */
|
|
59
|
+
bind_process?: string;
|
|
60
|
+
/** `swarm spawn`'s, which waits on the harness it runs: the spawner's stop may wait for that worker */
|
|
61
|
+
spawn?: boolean;
|
|
62
|
+
/** `swarm spawn --no-push`'s: the spawner's stop never waits on that worker, whose messages and handback stay ordinary unread ones */
|
|
63
|
+
no_push?: boolean;
|
|
64
|
+
/** with peek on, the spawn's prompt: kept scrubbed as the worker's first peek turn once it joins */
|
|
65
|
+
prompt?: string;
|
|
66
|
+
}
|
|
67
|
+
interface GroupParams {
|
|
68
|
+
action: "create" | "add" | "remove" | "rules" | "ls" | "show" | "drop" | "pin" | "unpin";
|
|
69
|
+
name?: string;
|
|
70
|
+
endpoint?: string;
|
|
71
|
+
in?: string[];
|
|
72
|
+
out?: string[];
|
|
73
|
+
rules?: string[];
|
|
74
|
+
}
|
|
75
|
+
/** one NDJSON JSON-RPC connection to the local broker */
|
|
76
|
+
declare class RelayClient {
|
|
77
|
+
private nextId;
|
|
78
|
+
private readonly pending;
|
|
79
|
+
private readonly reader;
|
|
80
|
+
private readonly writer;
|
|
81
|
+
private socket;
|
|
82
|
+
private closedError;
|
|
83
|
+
private listeners;
|
|
84
|
+
static connect(path: string): Promise<RelayClient>;
|
|
85
|
+
onNotification(fn: (method: string, params: unknown) => void): void;
|
|
86
|
+
request<T = unknown>(method: string, params?: object): Promise<T>;
|
|
87
|
+
hello(hello: Hello): Promise<HelloResult>;
|
|
88
|
+
rules(endpoint?: string): Promise<ScopeView>;
|
|
89
|
+
rulesSet(params: {
|
|
90
|
+
endpoint?: string;
|
|
91
|
+
in?: string[];
|
|
92
|
+
out?: string[];
|
|
93
|
+
scope?: ScopePreset;
|
|
94
|
+
allow?: string[];
|
|
95
|
+
}): Promise<ScopeView>;
|
|
96
|
+
rulesWiden(params: {
|
|
97
|
+
endpoint: string;
|
|
98
|
+
in?: string[];
|
|
99
|
+
out?: string[];
|
|
100
|
+
}): Promise<ScopeChange>;
|
|
101
|
+
rulesLift(params: {
|
|
102
|
+
endpoint: string;
|
|
103
|
+
cap: string;
|
|
104
|
+
}): Promise<ScopeView>;
|
|
105
|
+
/** a `krt_` grant for a spawn: `env` holds SCP_GRANT, which is a secret */
|
|
106
|
+
grant(params: GrantParams): Promise<{
|
|
107
|
+
token: string;
|
|
108
|
+
env: Record<string, string>;
|
|
109
|
+
steer?: {
|
|
110
|
+
key: string;
|
|
111
|
+
parent: string;
|
|
112
|
+
};
|
|
113
|
+
}>;
|
|
114
|
+
/**
|
|
115
|
+
* the harness a spawn ran with `grant` exited: its endpoint goes done and offline, or a grant no one spent is revoked, and the spawner gets
|
|
116
|
+
* `output` back in the handback message, unless the harness never `started`
|
|
117
|
+
*/
|
|
118
|
+
spawnExit(params: {
|
|
119
|
+
grant: string;
|
|
120
|
+
code: number;
|
|
121
|
+
output?: string;
|
|
122
|
+
stderr?: string;
|
|
123
|
+
unapplied?: string[];
|
|
124
|
+
exits?: number[];
|
|
125
|
+
started?: boolean;
|
|
126
|
+
}): Promise<{
|
|
127
|
+
joined: boolean;
|
|
128
|
+
name: string | null;
|
|
129
|
+
endpoint: EndpointView | null;
|
|
130
|
+
handback?: string | null;
|
|
131
|
+
}>;
|
|
132
|
+
/**
|
|
133
|
+
* a spawned worker that exited: its harness session id, the transcript its hooks named with it and its spawner's unread messages to it;
|
|
134
|
+
* with `take` and a session they are marked read, for a resume to carry them
|
|
135
|
+
*/
|
|
136
|
+
spawnSteers(params: {
|
|
137
|
+
grant: string;
|
|
138
|
+
take?: boolean;
|
|
139
|
+
}): Promise<{
|
|
140
|
+
session: string | null;
|
|
141
|
+
steers: Envelope[];
|
|
142
|
+
transcript?: string | null;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* at a stop: the spawned workers' unread messages, held for the hook to print, waiting up to `wait_s` while workers still run, none came,
|
|
146
|
+
* pushes are left and no prompt moved the `prompts` mark inject handed out; with `wait_s` above 0 and other workers still running, the
|
|
147
|
+
* broker gathers more of their results into the same push for up to ${LIMITS.pushGraceS} s, which may run past `wait_s`
|
|
148
|
+
*/
|
|
149
|
+
stopWait(waitS?: number, prompts?: number): Promise<StopWaitResult>;
|
|
150
|
+
group(params: GroupParams): Promise<unknown>;
|
|
151
|
+
admin(params: {
|
|
152
|
+
action: "grant" | "revoke" | "ls";
|
|
153
|
+
subject?: string;
|
|
154
|
+
tree?: string;
|
|
155
|
+
group?: string;
|
|
156
|
+
}): Promise<unknown>;
|
|
157
|
+
peers(): Promise<PeerView[]>;
|
|
158
|
+
why(from: string, to: string): Promise<WhyResult>;
|
|
159
|
+
close(): void;
|
|
160
|
+
private flush;
|
|
161
|
+
private onData;
|
|
162
|
+
private dispatchLines;
|
|
163
|
+
private fail;
|
|
164
|
+
private onClose;
|
|
165
|
+
}
|
|
166
|
+
declare class BrokerUnavailable extends Error {
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A connection to the broker for `dir`. With `start`, a missing broker is started (detached)
|
|
170
|
+
* and the connection retried for up to `timeoutMs`. A socket whose directory is a link, someone
|
|
171
|
+
* else's, or writable by others is refused before anything is sent: in /tmp anyone could make it first.
|
|
172
|
+
*/
|
|
173
|
+
declare function connectBroker(dir: string, start?: () => void, timeoutMs?: number): Promise<RelayClient>;
|
|
174
|
+
|
|
175
|
+
interface BridgeConfig {
|
|
176
|
+
/** the hub's https origin, from the caller; the socket is <origin>/v1/relay/ws */
|
|
177
|
+
url: string;
|
|
178
|
+
/** the account's bearer credential for the hub, opaque to the broker; null keeps the bridge down and says why in status() */
|
|
179
|
+
token: string | null;
|
|
180
|
+
/** why there is no credential, for status() */
|
|
181
|
+
tokenError?: string | undefined;
|
|
182
|
+
/** reads the credential again before each reconnect, so an expired or replaced one is picked up */
|
|
183
|
+
credentials?: (() => Promise<string | null>) | undefined;
|
|
184
|
+
machine: string;
|
|
185
|
+
/** which local endpoints are mirrored to the hub (default ["*"]) */
|
|
186
|
+
mirror?: Selector[] | undefined;
|
|
187
|
+
/** drop cwd, status notes and activity summaries from what is mirrored */
|
|
188
|
+
redact?: boolean | undefined;
|
|
189
|
+
/** let messages marked private leave this machine */
|
|
190
|
+
privateOk?: boolean | undefined;
|
|
191
|
+
/** a WebSocket constructor (tests) */
|
|
192
|
+
webSocket?: WebSocketWithHeaders | undefined;
|
|
193
|
+
/** reconnect backoff bounds (default 1 s to 60 s) */
|
|
194
|
+
backoff?: {
|
|
195
|
+
minMs: number;
|
|
196
|
+
maxMs: number;
|
|
197
|
+
} | undefined;
|
|
198
|
+
/** how long a forward cut off by a dropped connection waits for the next one (default 30 s) */
|
|
199
|
+
forwardWaitMs?: number | undefined;
|
|
200
|
+
/** the wait before a failed inject is tried again, doubling for each later try (default 1 s) */
|
|
201
|
+
injectBackoffMs?: number | undefined;
|
|
202
|
+
}
|
|
203
|
+
interface BridgeStatus {
|
|
204
|
+
enabled: true;
|
|
205
|
+
machine: string;
|
|
206
|
+
url: string;
|
|
207
|
+
connected: boolean;
|
|
208
|
+
last_error: string | null;
|
|
209
|
+
since: string;
|
|
210
|
+
}
|
|
211
|
+
interface Bridge {
|
|
212
|
+
/** a send whose recipients are all on other machines, on behalf of the local endpoint `from` */
|
|
213
|
+
forward(from: string, params: SendParams, origin?: {
|
|
214
|
+
tty?: boolean;
|
|
215
|
+
}): Promise<SendResult>;
|
|
216
|
+
/** a local endpoint was claimed, let go or changed its status: mirrored as a delta */
|
|
217
|
+
changed(endpoint: string): void;
|
|
218
|
+
/** rules, caps, groups or a title changed, which can touch several endpoints: every mirrored endpoint is sent again */
|
|
219
|
+
resync(): void;
|
|
220
|
+
/** a local endpoint's activity, posted to the hub */
|
|
221
|
+
activity(endpoint: string, row: ActivityRow): void;
|
|
222
|
+
/** the endpoints of other accounts this account's contacts let it reach */
|
|
223
|
+
contacts(): Promise<XListed[]>;
|
|
224
|
+
status(): BridgeStatus;
|
|
225
|
+
stop(): Promise<void>;
|
|
226
|
+
}
|
|
227
|
+
/** a WebSocket constructor that takes request headers, as Bun's and Node's do */
|
|
228
|
+
type WebSocketWithHeaders = new (url: string, options: {
|
|
229
|
+
headers: Record<string, string>;
|
|
230
|
+
}) => WebSocket;
|
|
231
|
+
/** `<origin>/v1/relay/ws` with ws or wss */
|
|
232
|
+
declare function relaySocketUrl(origin: string): string;
|
|
233
|
+
|
|
234
|
+
declare class BrokerRunning extends Error {
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** the longest request line the broker reads, in bytes */
|
|
238
|
+
declare const MAX_LINE: number;
|
|
239
|
+
|
|
240
|
+
type Run = (cmd: string[]) => {
|
|
241
|
+
exitCode: number | null;
|
|
242
|
+
stdout: string | Buffer;
|
|
243
|
+
};
|
|
244
|
+
/** macOS's LocalHostName (read only), or undefined when it is unset */
|
|
245
|
+
declare function localHostName(run?: Run): string | undefined;
|
|
246
|
+
/** this machine's name: on macOS its LocalHostName, which a network change does not rename as it can the hostname; else the hostname */
|
|
247
|
+
declare function systemHostname(platform?: string, local?: () => string | undefined, fallback?: () => string): string;
|
|
248
|
+
/** systemHostname, read once per process */
|
|
249
|
+
declare function thisHostname(): string;
|
|
250
|
+
/** the lowercased first label of `name` (default this machine's): agents' machine label, and what `machine:` matches */
|
|
251
|
+
declare function shortHostname(name?: string): string;
|
|
252
|
+
/** `$SCP_DIR`, else `$XDG_STATE_HOME/scp`, else ~/.local/state/scp */
|
|
253
|
+
declare function relayDir(env?: NodeJS.ProcessEnv): string;
|
|
254
|
+
/**
|
|
255
|
+
* `~/.config/scp/policy.json` of the account the broker runs as, from the user database rather
|
|
256
|
+
* than HOME or XDG_CONFIG_HOME, so the process that happens to start the broker cannot pick its policy
|
|
257
|
+
*/
|
|
258
|
+
declare function policyPath(): string;
|
|
259
|
+
/** the account's home directory from the user database, falling back to HOME where that is unavailable */
|
|
260
|
+
declare function accountHome(): string;
|
|
261
|
+
/** a private directory: created 0700, and refused when it is a link or someone else's */
|
|
262
|
+
declare function privateDir(path: string): string;
|
|
263
|
+
/**
|
|
264
|
+
* The broker's socket: inside the relay dir, unless that path is too long for a unix socket
|
|
265
|
+
* (deep temp HOMEs), then a per-user, per-dir private directory under /tmp.
|
|
266
|
+
*/
|
|
267
|
+
declare function socketPath(dir: string): string;
|
|
268
|
+
|
|
269
|
+
type JudgeClientFactory = (model: JudgeModelConfig, key: () => Promise<string>) => JudgeClient;
|
|
270
|
+
type Origin = {
|
|
271
|
+
tty?: boolean;
|
|
272
|
+
unverified?: boolean;
|
|
273
|
+
};
|
|
274
|
+
/** sends as `from` through the same path an unjudged send takes, waking recipients */
|
|
275
|
+
type Deliver = (from: string, params: SendParams, origin: Origin) => Promise<SendResult>;
|
|
276
|
+
interface JudgeGateOptions {
|
|
277
|
+
sql: Sql;
|
|
278
|
+
core: FederatedCore;
|
|
279
|
+
now: () => number;
|
|
280
|
+
/** where key_env is read */
|
|
281
|
+
env: Record<string, string | undefined>;
|
|
282
|
+
client?: JudgeClientFactory | undefined;
|
|
283
|
+
log: (line: string) => void;
|
|
284
|
+
/** an endpoint got a notice */
|
|
285
|
+
wake: (endpoint: string) => void;
|
|
286
|
+
}
|
|
287
|
+
interface HoldRow {
|
|
288
|
+
id: string;
|
|
289
|
+
from_ep: string;
|
|
290
|
+
params: string;
|
|
291
|
+
origin: string;
|
|
292
|
+
verdict: string;
|
|
293
|
+
created_at: number;
|
|
294
|
+
expires_at: number;
|
|
295
|
+
state: "held" | "releasing" | "released" | "refused" | "expired";
|
|
296
|
+
by: string | null;
|
|
297
|
+
resolved_at: number | null;
|
|
298
|
+
}
|
|
299
|
+
/** a hold as the human's review lists it: never the body */
|
|
300
|
+
interface HoldView {
|
|
301
|
+
id: string;
|
|
302
|
+
from: string;
|
|
303
|
+
to: string[];
|
|
304
|
+
state: HoldRow["state"];
|
|
305
|
+
created_at: string;
|
|
306
|
+
expires_at: string;
|
|
307
|
+
categories: string[];
|
|
308
|
+
reason: string;
|
|
309
|
+
boundary: string;
|
|
310
|
+
source: Verdict["source"];
|
|
311
|
+
bytes: number;
|
|
312
|
+
by: string | null;
|
|
313
|
+
resolved_at: string | null;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* the broker's local judge (policy `judge`): shadow judges after delivery and only audits; enforce judges before delivery and passes,
|
|
317
|
+
* redacts, refuses or holds. Holds wait in `judge_holds` for the human at a terminal; their body is purged once resolved or expired
|
|
318
|
+
*/
|
|
319
|
+
declare class JudgeGate {
|
|
320
|
+
private readonly o;
|
|
321
|
+
readonly stats: _scp_dev_core.JudgeStats;
|
|
322
|
+
private policy;
|
|
323
|
+
private version;
|
|
324
|
+
private client;
|
|
325
|
+
private readonly cache;
|
|
326
|
+
private readonly shadows;
|
|
327
|
+
/** judged sends in the last hour per budget: `ep:<id>`, `tree:<root id>` and `*` for the whole broker */
|
|
328
|
+
private readonly spent;
|
|
329
|
+
constructor(o: JudgeGateOptions);
|
|
330
|
+
/** the policy's judge section; a model client only when its key is readable, else the prefilter alone */
|
|
331
|
+
configure(policy: JudgePolicy | undefined): void;
|
|
332
|
+
private keyReader;
|
|
333
|
+
/** resolves once every shadow judgement started so far finished */
|
|
334
|
+
settled(): Promise<void>;
|
|
335
|
+
/** a send or ask through the judge; an unjudged one goes straight to `deliver` */
|
|
336
|
+
send(from: string, params: SendParams, origin: Origin, deliver: Deliver): Promise<SendResult>;
|
|
337
|
+
/** an earlier judged send or waiting hold under the send's client_id: the same message is answered as before, another one refused as core refuses it */
|
|
338
|
+
private seen;
|
|
339
|
+
private remember;
|
|
340
|
+
/**
|
|
341
|
+
* a judged send counts before any model call against its sender's, its lineage tree's and the whole broker's budget, since refused and
|
|
342
|
+
* held sends never reach the send's own limits, and a new handle or a helper process must not bring a fresh budget
|
|
343
|
+
*/
|
|
344
|
+
private spend;
|
|
345
|
+
/** how many budgets are tracked (idle ones are evicted by the sweep) */
|
|
346
|
+
budgetKeys(): number;
|
|
347
|
+
/** the held messages, newest first, without bodies */
|
|
348
|
+
holds(state?: string): HoldView[];
|
|
349
|
+
/** one hold with its body and the judge's redactions while it waits */
|
|
350
|
+
show(id: string): HoldView & {
|
|
351
|
+
body?: string;
|
|
352
|
+
redactions?: {
|
|
353
|
+
quote?: string;
|
|
354
|
+
kind: string;
|
|
355
|
+
}[];
|
|
356
|
+
};
|
|
357
|
+
/** delivers a held message as its sender, as stored or redacted: scope, policy and limits are checked again by the send itself */
|
|
358
|
+
release(id: string, options: {
|
|
359
|
+
redacted?: boolean;
|
|
360
|
+
by: string;
|
|
361
|
+
}, deliver: Deliver): Promise<SendResult>;
|
|
362
|
+
/** refuses a held message: it is never delivered, and its sender hears the human's reason */
|
|
363
|
+
refuse(id: string, reason: string, by: string): HoldView;
|
|
364
|
+
/** expires holds past their message TTL (body purged, sender told) and drops resolved ones past the audit window */
|
|
365
|
+
sweep(): void;
|
|
366
|
+
private expire;
|
|
367
|
+
/** the recipients by the boundary their message crosses; throws as send does for a target the sender cannot reach */
|
|
368
|
+
private boundaries;
|
|
369
|
+
private judge;
|
|
370
|
+
private shadow;
|
|
371
|
+
private hold;
|
|
372
|
+
private detail;
|
|
373
|
+
private storedDetail;
|
|
374
|
+
/** marks a hold resolved and purges its body and the judge's quotes of it */
|
|
375
|
+
private resolve;
|
|
376
|
+
private tell;
|
|
377
|
+
private row;
|
|
378
|
+
private view;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
interface BrokerOptions {
|
|
382
|
+
dir: string;
|
|
383
|
+
/** the policy file; missing means open */
|
|
384
|
+
policyPath?: string | undefined;
|
|
385
|
+
/** the user part of addresses */
|
|
386
|
+
user?: string | undefined;
|
|
387
|
+
/** exit after this long with no connection and nothing waiting (default 30 min); 0 never */
|
|
388
|
+
idleMs?: number | undefined;
|
|
389
|
+
now?: (() => number) | undefined;
|
|
390
|
+
/** extra roots attachments may point into, besides the sender's cwd */
|
|
391
|
+
roots?: string[] | undefined;
|
|
392
|
+
/** bytes queued for one connection before fleet events to it are dropped and replies end it (default 4 MiB) */
|
|
393
|
+
outboundCapBytes?: number | undefined;
|
|
394
|
+
/** requests one connection may have in flight (default 32) */
|
|
395
|
+
maxPending?: number | undefined;
|
|
396
|
+
/** how long a request line may stay unfinished (default 30 s) */
|
|
397
|
+
partialLineMs?: number | undefined;
|
|
398
|
+
/** where the broker reports its own failures (default stderr) */
|
|
399
|
+
log?: ((line: string) => void) | undefined;
|
|
400
|
+
/** this host's machine name on the hub (default the bridge's), and for `machine:` selectors (default the short hostname) */
|
|
401
|
+
machine?: string | undefined;
|
|
402
|
+
/** the link to the hub; without it the broker never opens a network connection */
|
|
403
|
+
bridge?: BridgeConfig | undefined;
|
|
404
|
+
/** how often presence that decays without a request (a hook-only endpoint) is checked for changes (default 60 s) */
|
|
405
|
+
presenceEveryMs?: number | undefined;
|
|
406
|
+
/** where a judge model's key_env is read (default the broker's environment) */
|
|
407
|
+
env?: Record<string, string | undefined> | undefined;
|
|
408
|
+
/** the judge model client for a policy's `judge.model` (default the provider's HTTP API) */
|
|
409
|
+
judgeClient?: JudgeClientFactory | undefined;
|
|
410
|
+
}
|
|
411
|
+
interface Broker {
|
|
412
|
+
socket: string;
|
|
413
|
+
core: FederatedCore;
|
|
414
|
+
bridge: Bridge | null;
|
|
415
|
+
judge: JudgeGate;
|
|
416
|
+
policyFile: string;
|
|
417
|
+
stopped: Promise<void>;
|
|
418
|
+
stop(): Promise<void>;
|
|
419
|
+
}
|
|
420
|
+
declare function startBroker(options: BrokerOptions): Promise<Broker>;
|
|
421
|
+
|
|
422
|
+
export { type Bridge, type BridgeConfig, type BridgeStatus, type Broker, type BrokerInfo, type BrokerOptions, BrokerRunning, BrokerUnavailable, type GrantParams, type GroupParams, type Hello, type HelloResult, MAX_LINE, RelayClient, accountHome, connectBroker, localHostName, policyPath, privateDir, relayDir, relaySocketUrl, shortHostname, socketPath, startBroker, systemHostname, thisHostname };
|