@scp-dev/broker 0.1.3 → 0.1.5
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/dist/index.d.ts +28 -115
- package/dist/index.js +120 -516
- package/package.json +3 -3
- package/src/bridge.ts +1 -0
- package/src/client.ts +2 -2
- package/src/index.ts +3 -2
- package/src/judge-gate.ts +14 -545
- package/src/server.ts +75 -19
package/src/server.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
stripControl,
|
|
27
27
|
} from "@scp-dev/core"
|
|
28
28
|
import { FederatedCore } from "@scp-dev/hub"
|
|
29
|
-
import { type Bridge, type BridgeConfig, remoteSend, startBridge } from "./bridge"
|
|
29
|
+
import { type Bridge, type BridgeConfig, type BridgeHooks, remoteSend, startBridge } from "./bridge"
|
|
30
30
|
import { type Deliver, type JudgeClientFactory, JudgeGate } from "./judge-gate"
|
|
31
31
|
import { releaseBrokerLock, takeBrokerLock } from "./lock"
|
|
32
32
|
import { encodeLine, LineReader, LineWriter, MAX_LINE } from "./ndjson"
|
|
@@ -60,6 +60,8 @@ export interface BrokerOptions {
|
|
|
60
60
|
bridge?: BridgeConfig | undefined
|
|
61
61
|
/** reads the bridge configuration again for bridge_relink; one that appeared or names another hub or machine restarts the broker */
|
|
62
62
|
bridgeConfig?: (() => Promise<BridgeConfig | undefined>) | undefined
|
|
63
|
+
/** experimental: another link to other machines in place of the hub's, started only when `bridge` is unset */
|
|
64
|
+
link?: LinkFactory | undefined
|
|
63
65
|
/** how often presence that decays without a request (a hook-only endpoint) is checked for changes (default 60 s) */
|
|
64
66
|
presenceEveryMs?: number | undefined
|
|
65
67
|
/** where a judge model's key_env is read (default the broker's environment) */
|
|
@@ -68,6 +70,15 @@ export interface BrokerOptions {
|
|
|
68
70
|
judgeClient?: JudgeClientFactory | undefined
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/** what a link gets from the broker: its core, the remote hooks a bridge gets, and a send as a local endpoint or a mirrored one through the judge */
|
|
74
|
+
export interface LinkContext {
|
|
75
|
+
core: FederatedCore
|
|
76
|
+
hooks: BridgeHooks
|
|
77
|
+
send: Deliver
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type LinkFactory = (context: LinkContext) => Bridge
|
|
81
|
+
|
|
71
82
|
export interface Broker {
|
|
72
83
|
socket: string
|
|
73
84
|
core: FederatedCore
|
|
@@ -88,6 +99,8 @@ interface Connection {
|
|
|
88
99
|
tty?: boolean
|
|
89
100
|
/** the connection holds the human inbox */
|
|
90
101
|
human?: boolean
|
|
102
|
+
/** a human hello bound to the agent of this shell's lineage said a terminal was attached: the human at that agent's shell */
|
|
103
|
+
terminal?: boolean
|
|
91
104
|
/**
|
|
92
105
|
* the lineage endpoint of an observer, or of a hello bound to it: the connection sees and acts with that endpoint's view,
|
|
93
106
|
* never widening unless `own`, and never counts as connected
|
|
@@ -135,7 +148,19 @@ const CHANGES_SCOPE = new Set(["rules_set", "rules_widen", "rules_lift", "group"
|
|
|
135
148
|
const ASK_RECHECK_MS = 1000
|
|
136
149
|
/** how often a waiting stop rechecks its workers, since a worker's exit or lapse wakes no one */
|
|
137
150
|
const STOP_RECHECK_MS = 1000
|
|
138
|
-
const TRANSIENT_METHODS = new Set([
|
|
151
|
+
const TRANSIENT_METHODS = new Set([
|
|
152
|
+
"event",
|
|
153
|
+
"status",
|
|
154
|
+
"whoami",
|
|
155
|
+
"inject",
|
|
156
|
+
"injected",
|
|
157
|
+
"peek_turn",
|
|
158
|
+
"stop_wait",
|
|
159
|
+
"prompted",
|
|
160
|
+
"title_infer",
|
|
161
|
+
"subagent_title",
|
|
162
|
+
"wake",
|
|
163
|
+
])
|
|
139
164
|
const GRANT_UNBOUND = `a spawn-env grant binds only a new harness process: run the harness with it, or name the child (${CLI_NAME} spawn-env --name / --as)`
|
|
140
165
|
|
|
141
166
|
/** the workers' messages and handbacks a stop's push would show, and those of theirs left unread past it */
|
|
@@ -269,21 +294,17 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
269
294
|
}
|
|
270
295
|
for (const id of known.keys()) if (!live.has(id)) known.delete(id)
|
|
271
296
|
}, options.presenceEveryMs ?? PRESENCE_MS)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
loadPolicy()
|
|
281
|
-
return policyError
|
|
282
|
-
},
|
|
283
|
-
log,
|
|
284
|
-
},
|
|
285
|
-
})
|
|
297
|
+
const remoteHooks: BridgeHooks = {
|
|
298
|
+
wake,
|
|
299
|
+
fleet: notifyFleet,
|
|
300
|
+
policyError: () => {
|
|
301
|
+
loadPolicy()
|
|
302
|
+
return policyError
|
|
303
|
+
},
|
|
304
|
+
log,
|
|
286
305
|
}
|
|
306
|
+
if (options.bridge) bridge = startBridge({ ...options.bridge, core, onRemote: remoteHooks })
|
|
307
|
+
else if (options.link) bridge = options.link({ core, hooks: remoteHooks, send: (from, params, origin) => judge.send(from, params, origin, deliver) })
|
|
287
308
|
|
|
288
309
|
/** links the bridge again with the credential on disk; a configuration this broker cannot take without a restart stops it */
|
|
289
310
|
async function relink(): Promise<object> {
|
|
@@ -697,18 +718,24 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
697
718
|
}
|
|
698
719
|
const join = params.join
|
|
699
720
|
if (join !== undefined && (typeof join !== "object" || join === null || Array.isArray(join)))
|
|
700
|
-
throw new RelayError("invalid_params", "join is {handle?}")
|
|
721
|
+
throw new RelayError("invalid_params", "join is {handle?, under?}")
|
|
722
|
+
const under = (join as { under?: unknown } | undefined)?.under
|
|
701
723
|
const before = new Map(core.list({ all: true }).map((view) => [view.id, view]))
|
|
702
724
|
const { endpoint, subagent, created } =
|
|
703
725
|
join === undefined
|
|
704
726
|
? core.claim((params.claim ?? {}) as Claim, { transient: true })
|
|
705
|
-
: {
|
|
727
|
+
: {
|
|
728
|
+
endpoint: core.joinSession((params.claim ?? {}) as Claim, (join as { handle?: unknown }).handle, under),
|
|
729
|
+
subagent: undefined,
|
|
730
|
+
created: undefined,
|
|
731
|
+
}
|
|
706
732
|
s.data.endpoint = endpoint.id
|
|
707
733
|
s.data.transient = true
|
|
708
734
|
const key = (params.claim as Claim | undefined)?.process
|
|
709
735
|
if (typeof key === "string") s.data.process = key
|
|
710
736
|
const was = before.get(endpoint.id)?.address
|
|
711
737
|
if (join !== undefined) membership(endpoint, core.sessionOf(endpoint.id), true, was?.slice(was.lastIndexOf("/") + 1))
|
|
738
|
+
if (under !== undefined && before.get(endpoint.id)?.parent !== endpoint.parent) bridge?.resync()
|
|
712
739
|
else if ((before.get(endpoint.id)?.presence ?? "offline") !== endpoint.presence) presenceChanged(endpoint)
|
|
713
740
|
return {
|
|
714
741
|
endpoint,
|
|
@@ -741,7 +768,10 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
741
768
|
const claim = params.claim as Claim | undefined
|
|
742
769
|
if (params.human === true && params.resume === undefined && params.lineage !== undefined) {
|
|
743
770
|
const placed = core.placeLineage(params.lineage)
|
|
744
|
-
if (placed)
|
|
771
|
+
if (placed) {
|
|
772
|
+
s.data.terminal = params.tty === true
|
|
773
|
+
return bindLineage(s, placed, params)
|
|
774
|
+
}
|
|
745
775
|
} else if (params.human !== true && params.resume === undefined && claim?.process === undefined && typeof claim?.handle === "string") {
|
|
746
776
|
const placed = claim.lineage === undefined ? null : core.placeLineage(claim.lineage, claim.handle)
|
|
747
777
|
if (placed && (claim.grant === undefined || core.grantBinds(claim.grant, placed))) {
|
|
@@ -853,11 +883,37 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
853
883
|
if (changed) bridge?.changed(me)
|
|
854
884
|
return { changed }
|
|
855
885
|
}
|
|
886
|
+
case "subagent_title": {
|
|
887
|
+
const changed = core.subagentTitle(me, params, hookOf(s))
|
|
888
|
+
if (changed) bridge?.changed(changed)
|
|
889
|
+
return { changed: changed !== null }
|
|
890
|
+
}
|
|
891
|
+
case "wake": {
|
|
892
|
+
if (s.data.lineage && !s.data.own) throw new RelayError("unauthorized", "only the endpoint's own session says how it wakes")
|
|
893
|
+
const changed = core.setWake(me, params.via, hookOf(s))
|
|
894
|
+
if (changed) bridge?.changed(me)
|
|
895
|
+
return { changed }
|
|
896
|
+
}
|
|
856
897
|
case "rename": {
|
|
857
898
|
const renamed = core.rename(actorOf(s), { ...(params.endpoint !== undefined ? { endpoint: params.endpoint } : {}), handle: params.handle })
|
|
858
899
|
membership(renamed.endpoint, renamed.session, true, renamed.from)
|
|
859
900
|
return renamed.endpoint
|
|
860
901
|
}
|
|
902
|
+
case "adopt": {
|
|
903
|
+
const actor = actorOf(s)
|
|
904
|
+
const adopted = core.adopt(
|
|
905
|
+
actor,
|
|
906
|
+
{ endpoint: params.endpoint, ...(params.parent !== undefined ? { parent: params.parent } : {}) },
|
|
907
|
+
{ tty: actor.kind === "endpoint" && s.data.terminal === true },
|
|
908
|
+
)
|
|
909
|
+
bridge?.resync()
|
|
910
|
+
return adopted
|
|
911
|
+
}
|
|
912
|
+
case "unlink": {
|
|
913
|
+
const unlinked = core.unlink(actorOf(s), params.endpoint !== undefined ? { endpoint: params.endpoint } : {})
|
|
914
|
+
bridge?.resync()
|
|
915
|
+
return unlinked
|
|
916
|
+
}
|
|
861
917
|
case "leave": {
|
|
862
918
|
const left = core.leaveSession(actorOf(s), params.endpoint !== undefined ? { endpoint: params.endpoint } : {})
|
|
863
919
|
for (const c of connections) if (c.data.endpoint === left.endpoint.id && !c.data.transient && !c.data.lineage && c !== s) c.end()
|