@scp-dev/broker 0.1.3 → 0.1.4

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/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
@@ -269,21 +282,17 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
269
282
  }
270
283
  for (const id of known.keys()) if (!live.has(id)) known.delete(id)
271
284
  }, options.presenceEveryMs ?? PRESENCE_MS)
272
- if (options.bridge) {
273
- bridge = startBridge({
274
- ...options.bridge,
275
- core,
276
- onRemote: {
277
- wake,
278
- fleet: notifyFleet,
279
- policyError: () => {
280
- loadPolicy()
281
- return policyError
282
- },
283
- log,
284
- },
285
- })
285
+ const remoteHooks: BridgeHooks = {
286
+ wake,
287
+ fleet: notifyFleet,
288
+ policyError: () => {
289
+ loadPolicy()
290
+ return policyError
291
+ },
292
+ log,
286
293
  }
294
+ if (options.bridge) bridge = startBridge({ ...options.bridge, core, onRemote: remoteHooks })
295
+ else if (options.link) bridge = options.link({ core, hooks: remoteHooks, send: (from, params, origin) => judge.send(from, params, origin, deliver) })
287
296
 
288
297
  /** links the bridge again with the credential on disk; a configuration this broker cannot take without a restart stops it */
289
298
  async function relink(): Promise<object> {
@@ -697,18 +706,24 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
697
706
  }
698
707
  const join = params.join
699
708
  if (join !== undefined && (typeof join !== "object" || join === null || Array.isArray(join)))
700
- throw new RelayError("invalid_params", "join is {handle?}")
709
+ throw new RelayError("invalid_params", "join is {handle?, under?}")
710
+ const under = (join as { under?: unknown } | undefined)?.under
701
711
  const before = new Map(core.list({ all: true }).map((view) => [view.id, view]))
702
712
  const { endpoint, subagent, created } =
703
713
  join === undefined
704
714
  ? core.claim((params.claim ?? {}) as Claim, { transient: true })
705
- : { endpoint: core.joinSession((params.claim ?? {}) as Claim, (join as { handle?: unknown }).handle), subagent: undefined, created: undefined }
715
+ : {
716
+ endpoint: core.joinSession((params.claim ?? {}) as Claim, (join as { handle?: unknown }).handle, under),
717
+ subagent: undefined,
718
+ created: undefined,
719
+ }
706
720
  s.data.endpoint = endpoint.id
707
721
  s.data.transient = true
708
722
  const key = (params.claim as Claim | undefined)?.process
709
723
  if (typeof key === "string") s.data.process = key
710
724
  const was = before.get(endpoint.id)?.address
711
725
  if (join !== undefined) membership(endpoint, core.sessionOf(endpoint.id), true, was?.slice(was.lastIndexOf("/") + 1))
726
+ if (under !== undefined && before.get(endpoint.id)?.parent !== endpoint.parent) bridge?.resync()
712
727
  else if ((before.get(endpoint.id)?.presence ?? "offline") !== endpoint.presence) presenceChanged(endpoint)
713
728
  return {
714
729
  endpoint,
@@ -741,7 +756,10 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
741
756
  const claim = params.claim as Claim | undefined
742
757
  if (params.human === true && params.resume === undefined && params.lineage !== undefined) {
743
758
  const placed = core.placeLineage(params.lineage)
744
- if (placed) return bindLineage(s, placed, params)
759
+ if (placed) {
760
+ s.data.terminal = params.tty === true
761
+ return bindLineage(s, placed, params)
762
+ }
745
763
  } else if (params.human !== true && params.resume === undefined && claim?.process === undefined && typeof claim?.handle === "string") {
746
764
  const placed = claim.lineage === undefined ? null : core.placeLineage(claim.lineage, claim.handle)
747
765
  if (placed && (claim.grant === undefined || core.grantBinds(claim.grant, placed))) {
@@ -858,6 +876,21 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
858
876
  membership(renamed.endpoint, renamed.session, true, renamed.from)
859
877
  return renamed.endpoint
860
878
  }
879
+ case "adopt": {
880
+ const actor = actorOf(s)
881
+ const adopted = core.adopt(
882
+ actor,
883
+ { endpoint: params.endpoint, ...(params.parent !== undefined ? { parent: params.parent } : {}) },
884
+ { tty: actor.kind === "endpoint" && s.data.terminal === true },
885
+ )
886
+ bridge?.resync()
887
+ return adopted
888
+ }
889
+ case "unlink": {
890
+ const unlinked = core.unlink(actorOf(s), params.endpoint !== undefined ? { endpoint: params.endpoint } : {})
891
+ bridge?.resync()
892
+ return unlinked
893
+ }
861
894
  case "leave": {
862
895
  const left = core.leaveSession(actorOf(s), params.endpoint !== undefined ? { endpoint: params.endpoint } : {})
863
896
  for (const c of connections) if (c.data.endpoint === left.endpoint.id && !c.data.transient && !c.data.lineage && c !== s) c.end()