@nanobpm/agentic 0.1.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.
Files changed (228) hide show
  1. package/README.md +22 -0
  2. package/dist/blackboard/family.d.ts +40 -0
  3. package/dist/blackboard/family.js +151 -0
  4. package/dist/blackboard/index.d.ts +21 -0
  5. package/dist/blackboard/index.js +19 -0
  6. package/dist/blackboard/schema.d.ts +30 -0
  7. package/dist/blackboard/schema.js +42 -0
  8. package/dist/blackboard/store.d.ts +138 -0
  9. package/dist/blackboard/store.js +216 -0
  10. package/dist/blackboard/test-db.d.ts +5 -0
  11. package/dist/blackboard/test-db.js +42 -0
  12. package/dist/channel/auth.d.ts +41 -0
  13. package/dist/channel/auth.js +67 -0
  14. package/dist/channel/clock.d.ts +11 -0
  15. package/dist/channel/clock.js +4 -0
  16. package/dist/channel/connection.d.ts +75 -0
  17. package/dist/channel/connection.js +14 -0
  18. package/dist/channel/dispatch.d.ts +46 -0
  19. package/dist/channel/dispatch.js +86 -0
  20. package/dist/channel/hub.d.ts +78 -0
  21. package/dist/channel/hub.js +157 -0
  22. package/dist/channel/index.d.ts +27 -0
  23. package/dist/channel/index.js +20 -0
  24. package/dist/channel/registry.d.ts +68 -0
  25. package/dist/channel/registry.js +84 -0
  26. package/dist/channel/ws-transport.d.ts +23 -0
  27. package/dist/channel/ws-transport.js +178 -0
  28. package/dist/cockpit/boot.d.ts +68 -0
  29. package/dist/cockpit/boot.js +202 -0
  30. package/dist/cockpit/fake-dom.d.ts +37 -0
  31. package/dist/cockpit/fake-dom.js +73 -0
  32. package/dist/cockpit/index.d.ts +27 -0
  33. package/dist/cockpit/index.js +27 -0
  34. package/dist/cockpit/relay-client.d.ts +52 -0
  35. package/dist/cockpit/relay-client.js +192 -0
  36. package/dist/cockpit/render.d.ts +58 -0
  37. package/dist/cockpit/render.js +122 -0
  38. package/dist/cockpit/terminal-session.d.ts +95 -0
  39. package/dist/cockpit/terminal-session.js +123 -0
  40. package/dist/cockpit/view.d.ts +79 -0
  41. package/dist/cockpit/view.js +58 -0
  42. package/dist/demand/c8-rest.d.ts +77 -0
  43. package/dist/demand/c8-rest.js +123 -0
  44. package/dist/demand/index.d.ts +24 -0
  45. package/dist/demand/index.js +24 -0
  46. package/dist/demand/model.d.ts +68 -0
  47. package/dist/demand/model.js +118 -0
  48. package/dist/demand/taskdef.d.ts +40 -0
  49. package/dist/demand/taskdef.js +67 -0
  50. package/dist/index.d.ts +17 -0
  51. package/dist/index.js +17 -0
  52. package/dist/presence/family.d.ts +40 -0
  53. package/dist/presence/family.js +166 -0
  54. package/dist/presence/index.d.ts +19 -0
  55. package/dist/presence/index.js +17 -0
  56. package/dist/presence/schema.d.ts +20 -0
  57. package/dist/presence/schema.js +32 -0
  58. package/dist/presence/store.d.ts +130 -0
  59. package/dist/presence/store.js +191 -0
  60. package/dist/presence/test-db.d.ts +5 -0
  61. package/dist/presence/test-db.js +42 -0
  62. package/dist/protocol/conformance/frames.d.ts +24 -0
  63. package/dist/protocol/conformance/frames.js +116 -0
  64. package/dist/protocol/conformance/index.d.ts +13 -0
  65. package/dist/protocol/conformance/index.js +13 -0
  66. package/dist/protocol/conformance/malformed.d.ts +14 -0
  67. package/dist/protocol/conformance/malformed.js +44 -0
  68. package/dist/protocol/conformance/tokens.d.ts +19 -0
  69. package/dist/protocol/conformance/tokens.js +49 -0
  70. package/dist/protocol/conformance/vocab.d.ts +23 -0
  71. package/dist/protocol/conformance/vocab.js +97 -0
  72. package/dist/protocol/families.d.ts +32 -0
  73. package/dist/protocol/families.js +45 -0
  74. package/dist/protocol/frame.d.ts +45 -0
  75. package/dist/protocol/frame.js +114 -0
  76. package/dist/protocol/hex.d.ts +7 -0
  77. package/dist/protocol/hex.js +26 -0
  78. package/dist/protocol/index.d.ts +23 -0
  79. package/dist/protocol/index.js +23 -0
  80. package/dist/protocol/lanes.d.ts +36 -0
  81. package/dist/protocol/lanes.js +40 -0
  82. package/dist/protocol/payloads.d.ts +64 -0
  83. package/dist/protocol/payloads.js +122 -0
  84. package/dist/protocol/token.d.ts +34 -0
  85. package/dist/protocol/token.js +81 -0
  86. package/dist/protocol/vocab/schema.d.ts +51 -0
  87. package/dist/protocol/vocab/schema.js +218 -0
  88. package/dist/relay/incarnation.d.ts +17 -0
  89. package/dist/relay/incarnation.js +50 -0
  90. package/dist/relay/index.d.ts +25 -0
  91. package/dist/relay/index.js +22 -0
  92. package/dist/relay/relay-family.d.ts +68 -0
  93. package/dist/relay/relay-family.js +272 -0
  94. package/dist/relay/ring.d.ts +49 -0
  95. package/dist/relay/ring.js +105 -0
  96. package/dist/relay/scheduler.d.ts +72 -0
  97. package/dist/relay/scheduler.js +180 -0
  98. package/dist/relay/validate.d.ts +29 -0
  99. package/dist/relay/validate.js +39 -0
  100. package/dist/transcript/index.d.ts +18 -0
  101. package/dist/transcript/index.js +17 -0
  102. package/dist/transcript/schema.d.ts +32 -0
  103. package/dist/transcript/schema.js +48 -0
  104. package/dist/transcript/store.d.ts +192 -0
  105. package/dist/transcript/store.js +347 -0
  106. package/dist/transcript/test-db.d.ts +5 -0
  107. package/dist/transcript/test-db.js +41 -0
  108. package/dist/vocab/core-vocab.d.ts +26 -0
  109. package/dist/vocab/core-vocab.js +67 -0
  110. package/dist/vocab/diversity.d.ts +78 -0
  111. package/dist/vocab/diversity.js +89 -0
  112. package/dist/vocab/index.d.ts +22 -0
  113. package/dist/vocab/index.js +22 -0
  114. package/dist/vocab/merge.d.ts +9 -0
  115. package/dist/vocab/merge.js +104 -0
  116. package/dist/vocab/requires.d.ts +49 -0
  117. package/dist/vocab/requires.js +107 -0
  118. package/dist/vocab/resolver.d.ts +62 -0
  119. package/dist/vocab/resolver.js +149 -0
  120. package/dist/vocab/serve.d.ts +39 -0
  121. package/dist/vocab/serve.js +36 -0
  122. package/package.json +108 -0
  123. package/page/cockpit.css +114 -0
  124. package/page/cockpit.page.json +33 -0
  125. package/page/embed.html +40 -0
  126. package/page/mount.js +78 -0
  127. package/page/standalone.html +43 -0
  128. package/src/blackboard/family.test.ts +280 -0
  129. package/src/blackboard/family.ts +208 -0
  130. package/src/blackboard/index.ts +42 -0
  131. package/src/blackboard/schema.test.ts +60 -0
  132. package/src/blackboard/schema.ts +44 -0
  133. package/src/blackboard/store.test.ts +189 -0
  134. package/src/blackboard/store.ts +331 -0
  135. package/src/blackboard/test-db.ts +47 -0
  136. package/src/channel/auth.test.ts +64 -0
  137. package/src/channel/auth.ts +101 -0
  138. package/src/channel/clock.ts +14 -0
  139. package/src/channel/connection.ts +77 -0
  140. package/src/channel/dispatch.test.ts +83 -0
  141. package/src/channel/dispatch.ts +102 -0
  142. package/src/channel/hub.test.ts +335 -0
  143. package/src/channel/hub.ts +222 -0
  144. package/src/channel/index.ts +55 -0
  145. package/src/channel/registry.test.ts +73 -0
  146. package/src/channel/registry.ts +137 -0
  147. package/src/channel/ws-transport.test.ts +234 -0
  148. package/src/channel/ws-transport.ts +212 -0
  149. package/src/cockpit/boot.test.ts +374 -0
  150. package/src/cockpit/boot.ts +280 -0
  151. package/src/cockpit/fake-dom.ts +90 -0
  152. package/src/cockpit/index.ts +63 -0
  153. package/src/cockpit/relay-client.test.ts +359 -0
  154. package/src/cockpit/relay-client.ts +234 -0
  155. package/src/cockpit/render.test.ts +149 -0
  156. package/src/cockpit/render.ts +194 -0
  157. package/src/cockpit/terminal-session.test.ts +252 -0
  158. package/src/cockpit/terminal-session.ts +194 -0
  159. package/src/cockpit/view.test.ts +117 -0
  160. package/src/cockpit/view.ts +140 -0
  161. package/src/demand/c8-rest.test.ts +140 -0
  162. package/src/demand/c8-rest.ts +167 -0
  163. package/src/demand/index.ts +42 -0
  164. package/src/demand/model.test.ts +197 -0
  165. package/src/demand/model.ts +183 -0
  166. package/src/demand/taskdef.test.ts +85 -0
  167. package/src/demand/taskdef.ts +78 -0
  168. package/src/index.ts +17 -0
  169. package/src/presence/family.test.ts +252 -0
  170. package/src/presence/family.ts +205 -0
  171. package/src/presence/index.ts +26 -0
  172. package/src/presence/schema.test.ts +53 -0
  173. package/src/presence/schema.ts +34 -0
  174. package/src/presence/store.test.ts +190 -0
  175. package/src/presence/store.ts +287 -0
  176. package/src/presence/test-db.test.ts +57 -0
  177. package/src/presence/test-db.ts +47 -0
  178. package/src/protocol/conformance/corpus.test.ts +66 -0
  179. package/src/protocol/conformance/frames.ts +142 -0
  180. package/src/protocol/conformance/index.ts +29 -0
  181. package/src/protocol/conformance/malformed.ts +59 -0
  182. package/src/protocol/conformance/tokens.ts +70 -0
  183. package/src/protocol/conformance/vocab.ts +122 -0
  184. package/src/protocol/families.ts +54 -0
  185. package/src/protocol/frame.test.ts +116 -0
  186. package/src/protocol/frame.ts +171 -0
  187. package/src/protocol/hex.ts +28 -0
  188. package/src/protocol/index.ts +84 -0
  189. package/src/protocol/lanes.test.ts +82 -0
  190. package/src/protocol/lanes.ts +54 -0
  191. package/src/protocol/payloads.test.ts +91 -0
  192. package/src/protocol/payloads.ts +201 -0
  193. package/src/protocol/token.test.ts +57 -0
  194. package/src/protocol/token.ts +123 -0
  195. package/src/protocol/vocab/schema.test.ts +67 -0
  196. package/src/protocol/vocab/schema.ts +281 -0
  197. package/src/relay/incarnation.test.ts +53 -0
  198. package/src/relay/incarnation.ts +54 -0
  199. package/src/relay/index.ts +34 -0
  200. package/src/relay/integration.test.ts +135 -0
  201. package/src/relay/relay-family.test.ts +236 -0
  202. package/src/relay/relay-family.ts +336 -0
  203. package/src/relay/ring.test.ts +138 -0
  204. package/src/relay/ring.ts +136 -0
  205. package/src/relay/scheduler.test.ts +233 -0
  206. package/src/relay/scheduler.ts +208 -0
  207. package/src/relay/validate.test.ts +43 -0
  208. package/src/relay/validate.ts +44 -0
  209. package/src/transcript/index.ts +33 -0
  210. package/src/transcript/integration.test.ts +108 -0
  211. package/src/transcript/schema.test.ts +69 -0
  212. package/src/transcript/schema.ts +51 -0
  213. package/src/transcript/store.test.ts +285 -0
  214. package/src/transcript/store.ts +530 -0
  215. package/src/transcript/test-db.ts +46 -0
  216. package/src/vocab/core-vocab.test.ts +34 -0
  217. package/src/vocab/core-vocab.ts +88 -0
  218. package/src/vocab/diversity.test.ts +153 -0
  219. package/src/vocab/diversity.ts +169 -0
  220. package/src/vocab/index.ts +55 -0
  221. package/src/vocab/merge.test.ts +73 -0
  222. package/src/vocab/merge.ts +117 -0
  223. package/src/vocab/requires.test.ts +69 -0
  224. package/src/vocab/requires.ts +155 -0
  225. package/src/vocab/resolver.test.ts +118 -0
  226. package/src/vocab/resolver.ts +187 -0
  227. package/src/vocab/serve.test.ts +64 -0
  228. package/src/vocab/serve.ts +66 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The vocab resolver — S3's REGISTER→SERVE core.
3
+ *
4
+ * The versioned vocab artifact (S0 {@link VocabDocument}) is the ONE
5
+ * capability→token map; no map is ever baked into a worker. This resolver reads
6
+ * it and, given a declared enrolment capability, produces the deterministic
7
+ * SERVE token set the worker is entitled to fill — the `serve` reply to a
8
+ * `register` handshake.
9
+ *
10
+ * Token derivation walks `networks → subnetworks → roles`, joining segments into
11
+ * a routing token `network[.subnetwork…].role` (never a seat, never a
12
+ * capability). One exception encodes a BARE (network-less) role such as
13
+ * `decide`: a top-level role whose name equals its containing network and that
14
+ * has no subnetworks collapses to the single-segment token `role` — the only way
15
+ * the S0 network→role schema can express the bare tokens the grammar allows. So
16
+ * `networks.decide.roles.decide` serves the token `decide`.
17
+ */
18
+ import { formatToken, parseToken, validateVocabDocument } from "../protocol/index.js";
19
+ import { parseRequiresList, RequiresParseError, satisfiesRequires } from "./requires.js";
20
+ /** Raised when the document handed to the resolver is not a valid vocab artifact. */
21
+ export class VocabDocumentError extends Error {
22
+ errors;
23
+ constructor(errors) {
24
+ super(`invalid vocab document: ${errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
25
+ this.name = "VocabDocumentError";
26
+ this.errors = errors;
27
+ }
28
+ }
29
+ /**
30
+ * Parse a role's `requires` gate, tagging any failure with the routing token so a
31
+ * malformed predicate names the role that carries it (e.g. when merging author
32
+ * extensions) instead of surfacing a bare, context-free {@link RequiresParseError}.
33
+ */
34
+ function parseRequires(token, requires) {
35
+ try {
36
+ return parseRequiresList(requires);
37
+ }
38
+ catch (error) {
39
+ if (error instanceof RequiresParseError) {
40
+ throw new VocabDocumentError([{ path: token, message: error.message }]);
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ function deriveToken(network, subnetworks, role) {
46
+ // A self-named top-level role denotes a rootless (network-less) role: the only
47
+ // way the network→role schema can express a single-segment token like `decide`.
48
+ if (subnetworks.length === 0 && network === role) {
49
+ return formatToken({ subnetworks: [], role });
50
+ }
51
+ return formatToken({ network, subnetworks: [...subnetworks], role });
52
+ }
53
+ function normaliseSeats(seats) {
54
+ if (seats === undefined)
55
+ return 1;
56
+ if (typeof seats === "number")
57
+ return seats;
58
+ return [...seats];
59
+ }
60
+ export class VocabResolver {
61
+ #roles;
62
+ #byToken;
63
+ #version;
64
+ /**
65
+ * Build a resolver over a vocab document. The document is re-validated against
66
+ * the S0 schema and every role's `requires` gate is parsed up front, so a
67
+ * malformed gate fails loudly at construction rather than silently at match.
68
+ */
69
+ constructor(doc) {
70
+ const validation = validateVocabDocument(doc);
71
+ if (!validation.ok) {
72
+ throw new VocabDocumentError(validation.errors.map((e) => ({ path: e.path, message: e.message })));
73
+ }
74
+ const document = validation.value;
75
+ this.#version = document.version;
76
+ const roles = [];
77
+ const collect = (network, subnetworks, node) => {
78
+ if (node.roles !== undefined) {
79
+ for (const [roleName, role] of Object.entries(node.roles)) {
80
+ const token = deriveToken(network, subnetworks, roleName);
81
+ roles.push({
82
+ token,
83
+ ...(subnetworks.length === 0 && network === roleName ? {} : { network }),
84
+ subnetworks: [...subnetworks],
85
+ role: roleName,
86
+ ...(role.weight === undefined ? {} : { weight: role.weight }),
87
+ seats: normaliseSeats(role.seats),
88
+ seatsDistinctFamily: role.seatsDistinctFamily === true,
89
+ requires: parseRequires(token, role.requires),
90
+ });
91
+ }
92
+ }
93
+ if (node.subnetworks !== undefined) {
94
+ for (const [subName, sub] of Object.entries(node.subnetworks)) {
95
+ collect(network, [...subnetworks, subName], sub);
96
+ }
97
+ }
98
+ };
99
+ for (const [networkName, network] of Object.entries(document.networks)) {
100
+ collect(networkName, [], network);
101
+ }
102
+ // Deterministic order + a token→role index; a duplicate token is a modelling
103
+ // error (two roles cannot claim one routing token).
104
+ roles.sort((a, b) => (a.token < b.token ? -1 : a.token > b.token ? 1 : 0));
105
+ const byToken = new Map();
106
+ for (const role of roles) {
107
+ if (byToken.has(role.token)) {
108
+ throw new VocabDocumentError([{ path: role.token, message: `duplicate routing token: ${role.token}` }]);
109
+ }
110
+ byToken.set(role.token, role);
111
+ }
112
+ this.#roles = roles;
113
+ this.#byToken = byToken;
114
+ }
115
+ /** The vocab artifact version this resolver was built from. */
116
+ get version() {
117
+ return this.#version;
118
+ }
119
+ /** Every role in the vocab, sorted by token. */
120
+ roles() {
121
+ return this.#roles;
122
+ }
123
+ /** Every leaf token in the vocab, sorted. */
124
+ tokens() {
125
+ return this.#roles.map((role) => role.token);
126
+ }
127
+ /** Look up a role by its routing token. */
128
+ roleForToken(token) {
129
+ // Normalise through the grammar so `planning.planner` and any equivalent
130
+ // spelling resolve identically.
131
+ let key;
132
+ try {
133
+ key = formatToken(parseToken(token));
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ return this.#byToken.get(key);
139
+ }
140
+ /**
141
+ * Resolve a declared enrolment capability to its SERVE token set: every role
142
+ * whose `requires` gate the capability satisfies. The token list is sorted and
143
+ * de-duplicated, so the same capability always yields the same SERVE.
144
+ */
145
+ resolve(capability) {
146
+ const matched = this.#roles.filter((role) => satisfiesRequires(role.requires, capability));
147
+ return { tokens: matched.map((role) => role.token), roles: matched };
148
+ }
149
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The REGISTER→SERVE handshake mechanics.
3
+ *
4
+ * S2 owns the `register` family on the hub (one family, one owning module — this
5
+ * slice does NOT claim it). S3 supplies the other half of the handshake: given a
6
+ * declared enrolment capability, resolve it (via {@link VocabResolver}) to its
7
+ * SERVE token set and emit a `serve` frame back to the worker.
8
+ *
9
+ * The `serve` reply rides the CONTROL lane — it is a control/facts message, so a
10
+ * bulk relay storm can never delay a worker learning which tokens it may serve
11
+ * (S0 invariant 5). The composition root wires this after presence register
12
+ * (e.g. from S2's register path or an app-level adapter); keeping it a pure,
13
+ * connection-agnostic helper is what lets it compose without editing S2.
14
+ */
15
+ import type { Capability, Frame, ServePayload } from "../protocol/index.ts";
16
+ import type { Resolution, VocabResolver } from "./resolver.ts";
17
+ /** The minimal per-connection sink a serve reply needs — S1's `HubConnection.send`. */
18
+ export interface ServeSink {
19
+ send(frame: Frame): void;
20
+ }
21
+ /** Build the `serve` payload for an instance and its resolved token set. */
22
+ export declare function buildServePayload(instance: string, tokens: readonly string[]): ServePayload;
23
+ /**
24
+ * Build the `serve` frame carrying an instance's SERVE token set. Rides the
25
+ * control lane; `seq` defaults to 0 (the caller owns per-connection sequencing).
26
+ * The built payload is validated against the S0 `serve` contract, so a
27
+ * malformed token set fails loudly here rather than on the wire. `seq` is
28
+ * likewise checked against the wire's uint32 bound (the same {@link MAX_SEQ}
29
+ * `encodeFrame` enforces) so a bad sequence number fails here — at construction
30
+ * — rather than later during encoding.
31
+ */
32
+ export declare function buildServeFrame(instance: string, tokens: readonly string[], seq?: number): Frame;
33
+ /**
34
+ * Resolve `capability` and send the resulting `serve` frame to `sink`. Returns
35
+ * the {@link Resolution} so the caller can also record the assigned tokens (e.g.
36
+ * for demand×supply or diversity correlation). This is the server side of
37
+ * `REGISTER {capability}` → `SERVE [leaf tokens]`.
38
+ */
39
+ export declare function serveCapability(resolver: VocabResolver, sink: ServeSink, instance: string, capability: Capability, seq?: number): Resolution;
@@ -0,0 +1,36 @@
1
+ import { MAX_SEQ, validatePayload } from "../protocol/index.js";
2
+ /** Build the `serve` payload for an instance and its resolved token set. */
3
+ export function buildServePayload(instance, tokens) {
4
+ return { instance, tokens: [...tokens] };
5
+ }
6
+ /**
7
+ * Build the `serve` frame carrying an instance's SERVE token set. Rides the
8
+ * control lane; `seq` defaults to 0 (the caller owns per-connection sequencing).
9
+ * The built payload is validated against the S0 `serve` contract, so a
10
+ * malformed token set fails loudly here rather than on the wire. `seq` is
11
+ * likewise checked against the wire's uint32 bound (the same {@link MAX_SEQ}
12
+ * `encodeFrame` enforces) so a bad sequence number fails here — at construction
13
+ * — rather than later during encoding.
14
+ */
15
+ export function buildServeFrame(instance, tokens, seq = 0) {
16
+ if (!Number.isInteger(seq) || seq < 0 || seq > MAX_SEQ) {
17
+ throw new Error(`refusing to build serve frame with invalid seq (must be a uint32): ${String(seq)}`);
18
+ }
19
+ const payload = buildServePayload(instance, tokens);
20
+ const check = validatePayload("serve", payload);
21
+ if (!check.ok) {
22
+ throw new Error(`refusing to build invalid serve frame: ${check.errors.map((e) => e.message).join("; ")}`);
23
+ }
24
+ return { lane: "control", family: "serve", seq, payload };
25
+ }
26
+ /**
27
+ * Resolve `capability` and send the resulting `serve` frame to `sink`. Returns
28
+ * the {@link Resolution} so the caller can also record the assigned tokens (e.g.
29
+ * for demand×supply or diversity correlation). This is the server side of
30
+ * `REGISTER {capability}` → `SERVE [leaf tokens]`.
31
+ */
32
+ export function serveCapability(resolver, sink, instance, capability, seq = 0) {
33
+ const resolution = resolver.resolve(capability);
34
+ sink.send(buildServeFrame(instance, resolution.tokens, seq));
35
+ return resolution;
36
+ }
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@nanobpm/agentic",
3
+ "version": "0.1.0",
4
+ "description": "The Nano agentic protocol (ADR 0056): one app-tier channel carrying agent presence/registry, demand×supply, a shared blackboard and live terminal relay — with the wire contract, channel/hub, family modules and the operator cockpit, as subpath exports.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "keywords": [
8
+ "nanobpm",
9
+ "agentic",
10
+ "protocol",
11
+ "channel",
12
+ "presence",
13
+ "demand",
14
+ "relay",
15
+ "transcript",
16
+ "blackboard",
17
+ "cockpit",
18
+ "conformance"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nanobpm/nano-ide.git",
26
+ "directory": "packages/agentic"
27
+ },
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./source": "./src/index.ts",
36
+ "./protocol": {
37
+ "types": "./dist/protocol/index.d.ts",
38
+ "default": "./dist/protocol/index.js"
39
+ },
40
+ "./source/protocol": "./src/protocol/index.ts",
41
+ "./channel": {
42
+ "types": "./dist/channel/index.d.ts",
43
+ "default": "./dist/channel/index.js"
44
+ },
45
+ "./source/channel": "./src/channel/index.ts",
46
+ "./presence": {
47
+ "types": "./dist/presence/index.d.ts",
48
+ "default": "./dist/presence/index.js"
49
+ },
50
+ "./source/presence": "./src/presence/index.ts",
51
+ "./vocab": {
52
+ "types": "./dist/vocab/index.d.ts",
53
+ "default": "./dist/vocab/index.js"
54
+ },
55
+ "./source/vocab": "./src/vocab/index.ts",
56
+ "./demand": {
57
+ "types": "./dist/demand/index.d.ts",
58
+ "default": "./dist/demand/index.js"
59
+ },
60
+ "./source/demand": "./src/demand/index.ts",
61
+ "./relay": {
62
+ "types": "./dist/relay/index.d.ts",
63
+ "default": "./dist/relay/index.js"
64
+ },
65
+ "./source/relay": "./src/relay/index.ts",
66
+ "./transcript": {
67
+ "types": "./dist/transcript/index.d.ts",
68
+ "default": "./dist/transcript/index.js"
69
+ },
70
+ "./source/transcript": "./src/transcript/index.ts",
71
+ "./blackboard": {
72
+ "types": "./dist/blackboard/index.d.ts",
73
+ "default": "./dist/blackboard/index.js"
74
+ },
75
+ "./source/blackboard": "./src/blackboard/index.ts",
76
+ "./cockpit": {
77
+ "types": "./dist/cockpit/index.d.ts",
78
+ "default": "./dist/cockpit/index.js"
79
+ },
80
+ "./source/cockpit": "./src/cockpit/index.ts",
81
+ "./protocol/conformance": {
82
+ "types": "./dist/protocol/conformance/index.d.ts",
83
+ "default": "./dist/protocol/conformance/index.js"
84
+ },
85
+ "./source/protocol/conformance": "./src/protocol/conformance/index.ts"
86
+ },
87
+ "files": [
88
+ "dist",
89
+ "src",
90
+ "page",
91
+ "README.md"
92
+ ],
93
+ "scripts": {
94
+ "build": "tsc -p tsconfig.build.json",
95
+ "prepack": "npm run build",
96
+ "typecheck": "tsc --noEmit -p tsconfig.json",
97
+ "test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
98
+ "test:conformance": "node --test --experimental-strip-types \"src/protocol/conformance/corpus.test.ts\""
99
+ },
100
+ "dependencies": {
101
+ "ws": "^8.18.3"
102
+ },
103
+ "devDependencies": {
104
+ "@types/node": "^22.20.1",
105
+ "@types/ws": "^8.5.12",
106
+ "typescript": "^5.9.3"
107
+ }
108
+ }
@@ -0,0 +1,114 @@
1
+ /* The cockpit stylesheet — a compact, phone-friendly dark cockpit shared by the
2
+ standalone shell and the console App-View embed, so the two render identically.
3
+ The three-state SLO lights key off `data-status` (green | amber | red). */
4
+
5
+ :root {
6
+ --cockpit-bg: #0b0f14;
7
+ --cockpit-panel: #131a22;
8
+ --cockpit-edge: #223041;
9
+ --cockpit-text: #e6edf3;
10
+ --cockpit-muted: #8b98a5;
11
+ --cockpit-green: #2ea043;
12
+ --cockpit-amber: #d29922;
13
+ --cockpit-red: #f85149;
14
+ }
15
+
16
+ .cockpit-shell {
17
+ color: var(--cockpit-text);
18
+ background: var(--cockpit-bg);
19
+ font: 14px/1.4 ui-sans-serif, system-ui, sans-serif;
20
+ display: grid;
21
+ gap: 12px;
22
+ padding: 12px;
23
+ min-height: 100%;
24
+ }
25
+
26
+ .cockpit-header {
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ align-items: center;
30
+ gap: 12px;
31
+ }
32
+
33
+ .cockpit-title {
34
+ font-size: 16px;
35
+ margin: 0;
36
+ }
37
+
38
+ .cockpit-status {
39
+ padding: 2px 8px;
40
+ border-radius: 999px;
41
+ border: 1px solid var(--cockpit-edge);
42
+ font-variant: small-caps;
43
+ }
44
+
45
+ .cockpit-dot {
46
+ display: inline-block;
47
+ width: 10px;
48
+ height: 10px;
49
+ border-radius: 50%;
50
+ margin-right: 6px;
51
+ background: var(--cockpit-muted);
52
+ vertical-align: middle;
53
+ }
54
+
55
+ [data-status="green"] > .cockpit-dot,
56
+ .cockpit-dot[data-status="green"] { background: var(--cockpit-green); }
57
+ [data-status="amber"] > .cockpit-dot,
58
+ .cockpit-dot[data-status="amber"] { background: var(--cockpit-amber); }
59
+ [data-status="red"] > .cockpit-dot,
60
+ .cockpit-dot[data-status="red"] { background: var(--cockpit-red); }
61
+
62
+ .cockpit-status[data-status="green"] { color: var(--cockpit-green); }
63
+ .cockpit-status[data-status="amber"] { color: var(--cockpit-amber); }
64
+ .cockpit-status[data-status="red"] { color: var(--cockpit-red); }
65
+
66
+ .cockpit-missing {
67
+ background: var(--cockpit-panel);
68
+ border: 1px solid var(--cockpit-edge);
69
+ border-radius: 8px;
70
+ padding: 10px;
71
+ }
72
+ .cockpit-missing[data-empty="true"] { opacity: 0.7; }
73
+ .cockpit-panel-title { font-size: 13px; margin: 0 0 8px; color: var(--cockpit-muted); }
74
+ .cockpit-light { display: flex; align-items: center; gap: 6px; padding: 2px 0; }
75
+ .cockpit-light-detail { color: var(--cockpit-muted); font-size: 12px; }
76
+
77
+ .cockpit-matrix { display: grid; gap: 10px; }
78
+ .cockpit-network {
79
+ background: var(--cockpit-panel);
80
+ border: 1px solid var(--cockpit-edge);
81
+ border-left: 3px solid var(--cockpit-muted);
82
+ border-radius: 8px;
83
+ padding: 10px;
84
+ }
85
+ .cockpit-network[data-status="green"] { border-left-color: var(--cockpit-green); }
86
+ .cockpit-network[data-status="red"] { border-left-color: var(--cockpit-red); }
87
+ .cockpit-network-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
88
+ .cockpit-network-name { font-weight: 600; }
89
+ .cockpit-network-missing { color: var(--cockpit-red); font-size: 12px; }
90
+
91
+ .cockpit-tokens { width: 100%; border-collapse: collapse; }
92
+ .cockpit-th { text-align: left; color: var(--cockpit-muted); font-weight: 500; font-size: 12px; padding: 2px 6px; }
93
+ .cockpit-td { padding: 3px 6px; border-top: 1px solid var(--cockpit-edge); vertical-align: top; }
94
+ .cockpit-token[data-status="red"] .cockpit-token-name { color: var(--cockpit-red); }
95
+ .cockpit-workers { display: flex; flex-wrap: wrap; gap: 4px; }
96
+ .cockpit-worker {
97
+ background: #1c2733;
98
+ color: var(--cockpit-text);
99
+ border: 1px solid var(--cockpit-edge);
100
+ border-radius: 6px;
101
+ padding: 1px 8px;
102
+ cursor: pointer;
103
+ font: inherit;
104
+ }
105
+ .cockpit-worker:hover { border-color: var(--cockpit-green); }
106
+
107
+ .cockpit-terminal {
108
+ background: #000;
109
+ border: 1px solid var(--cockpit-edge);
110
+ border-radius: 8px;
111
+ padding: 8px;
112
+ }
113
+ .cockpit-terminal-host { min-height: 220px; }
114
+ .cockpit-nonagentic { color: var(--cockpit-muted); font-size: 12px; }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "title": "Cockpit",
4
+ "nodes": [
5
+ {
6
+ "type": "nav",
7
+ "id": "nav",
8
+ "props": {
9
+ "variant": "bar",
10
+ "title": "Agent cockpit",
11
+ "items": [{ "label": "Cockpit", "page": "cockpit" }]
12
+ }
13
+ },
14
+ {
15
+ "type": "text",
16
+ "id": "intro",
17
+ "props": {
18
+ "text": "Live demand×supply across the agent networks, missing-agent lights, the diversity SLO, and a drill-into-a-worker live terminal over the relay. The same view renders embedded in the console (App View) and standalone on a phone.",
19
+ "variant": "sub"
20
+ }
21
+ },
22
+ {
23
+ "type": "appView",
24
+ "id": "cockpit",
25
+ "props": {
26
+ "title": "Agent networks",
27
+ "embed": "./embed.html",
28
+ "standalone": "./standalone.html",
29
+ "fill": true
30
+ }
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,40 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Cockpit — App View embed</title>
7
+ <link rel="stylesheet" href="./cockpit.css" />
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css" />
9
+ <style>
10
+ html, body { margin: 0; height: 100%; background: #0b0f14; }
11
+ </style>
12
+ <script type="importmap">
13
+ {
14
+ "imports": {
15
+ "@nanobpm/agentic/cockpit": "../dist/cockpit/index.js",
16
+ "@xterm/xterm": "https://cdn.jsdelivr.net/npm/@xterm/xterm@5/+esm"
17
+ }
18
+ }
19
+ </script>
20
+ </head>
21
+ <body>
22
+ <!--
23
+ Console App-View embed. The console loads this document into its App-View
24
+ surface (ADR 0057) and hands it a host element; we mount the SAME cockpit
25
+ via the SAME page/mount.js as the standalone shell — only the host and the
26
+ injected endpoints differ, so the page renders identically. When the console
27
+ injects endpoint config via `window.__NANO_APP_VIEW__`, it is honoured.
28
+ -->
29
+ <main id="cockpit-root"></main>
30
+ <script type="module">
31
+ import { mountCockpit } from "./mount.js";
32
+
33
+ const cfg = window.__NANO_APP_VIEW__ ?? {};
34
+ mountCockpit(cfg.host ?? document.getElementById("cockpit-root"), {
35
+ reportUrl: cfg.reportUrl,
36
+ relayUrl: cfg.relayUrl,
37
+ });
38
+ </script>
39
+ </body>
40
+ </html>
package/page/mount.js ADDED
@@ -0,0 +1,78 @@
1
+ // Browser adapter for @nanobpm/agentic/cockpit (S8).
2
+ //
3
+ // This is the ONE wiring both the standalone shell and the console App-View embed
4
+ // call — they differ only in the host element they pass, so the cockpit renders
5
+ // identically embedded and standalone. It supplies the browser capabilities the
6
+ // injection-based core (`bootCockpit`) needs: the real `document`, a `fetch`-based
7
+ // S4 demand report source, a `WebSocket` relay socket factory, and an xterm.js
8
+ // terminal sink. It is plain browser ESM — not part of the typechecked/linted src.
9
+ //
10
+ // Resolve `@nanobpm/agentic/cockpit` and `@xterm/xterm` via the host page's import
11
+ // map (see standalone.html / embed.html).
12
+ import { bootCockpit } from "@nanobpm/agentic/cockpit";
13
+ import { Terminal } from "@xterm/xterm";
14
+
15
+ /** An xterm.js-backed terminal sink mounted into `host`. */
16
+ function xtermSink(host) {
17
+ const term = new Terminal({ convertEol: true, fontFamily: "ui-monospace, monospace", fontSize: 13 });
18
+ term.open(host);
19
+ return { write: (chunk) => term.write(chunk), dispose: () => term.dispose() };
20
+ }
21
+
22
+ /** A WebSocket relay socket factory for the agentic channel at `url`. */
23
+ function relaySocketFactory(url) {
24
+ return () => {
25
+ const ws = new WebSocket(url);
26
+ ws.binaryType = "arraybuffer";
27
+ return {
28
+ send: (bytes) => ws.send(bytes),
29
+ close: () => ws.close(),
30
+ onMessage: (cb) => ws.addEventListener("message", (event) => cb(new Uint8Array(event.data))),
31
+ onOpen: (cb) => ws.addEventListener("open", () => cb()),
32
+ onClose: (cb) => ws.addEventListener("close", () => cb()),
33
+ };
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Mount the cockpit into `host` and start polling.
39
+ *
40
+ * @param {Element} host — where the cockpit renders (standalone: document.body; embedded: the App-View host).
41
+ * @param {object} [opts]
42
+ * @param {string} [opts.reportUrl] — the S4 demand×supply JSON endpoint the app serves.
43
+ * @param {string} [opts.relayUrl] — the agentic channel WebSocket URL (with auth token + capability query).
44
+ * @param {number} [opts.refreshMs] — poll interval (default 2000).
45
+ * @returns the CockpitHandle (call `.dispose()` to tear down).
46
+ */
47
+ export function mountCockpit(host, opts = {}) {
48
+ if (host == null || typeof host.replaceChildren !== "function") {
49
+ throw new Error(
50
+ "mountCockpit(host): host must be a mounted DOM element (got " +
51
+ (host === null ? "null" : typeof host) +
52
+ "). Check that the cockpit root element exists before mounting.",
53
+ );
54
+ }
55
+ const reportUrl = opts.reportUrl ?? "/app/api/agentic/demand";
56
+ const relayUrl = opts.relayUrl ?? defaultRelayUrl();
57
+ const cockpit = bootCockpit({
58
+ host,
59
+ doc: document,
60
+ fetchReport: async () => {
61
+ const res = await fetch(reportUrl, { headers: { accept: "application/json" } });
62
+ if (!res.ok) throw new Error(`demand fetch failed: ${res.status}`);
63
+ return res.json();
64
+ },
65
+ connectRelay: relaySocketFactory(relayUrl),
66
+ createTerminal: xtermSink,
67
+ refreshMs: opts.refreshMs ?? 2000,
68
+ onError: (err) => console.error("[cockpit]", err),
69
+ });
70
+ cockpit.start();
71
+ return cockpit;
72
+ }
73
+
74
+ /** Derive the channel WebSocket URL from the current origin (path `/agentic`). */
75
+ function defaultRelayUrl() {
76
+ const proto = location.protocol === "https:" ? "wss:" : "ws:";
77
+ return `${proto}//${location.host}/agentic`;
78
+ }
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <title>Agent networks — the cockpit</title>
7
+ <link rel="stylesheet" href="./cockpit.css" />
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css" />
9
+ <style>
10
+ html, body { margin: 0; height: 100%; background: #0b0f14; }
11
+ </style>
12
+ <!--
13
+ Resolve the cockpit ESM + xterm.js. The cockpit is mapped RELATIVE to this
14
+ page (../dist/index.js) — exactly like ./mount.js and ./cockpit.css above —
15
+ so it resolves against whatever origin/version served the page (a CDN pin,
16
+ the Urban runtime, or a local checkout) with no hard-coded version to drift
17
+ out of sync with package.json. xterm.js stays a pinned third-party CDN dep.
18
+ The SAME page/mount.js the console App-View embed uses is loaded here.
19
+ -->
20
+ <script type="importmap">
21
+ {
22
+ "imports": {
23
+ "@nanobpm/agentic/cockpit": "../dist/cockpit/index.js",
24
+ "@xterm/xterm": "https://cdn.jsdelivr.net/npm/@xterm/xterm@5/+esm"
25
+ }
26
+ }
27
+ </script>
28
+ </head>
29
+ <body>
30
+ <main id="cockpit-root"></main>
31
+ <script type="module">
32
+ import { mountCockpit } from "./mount.js";
33
+
34
+ // Standalone: mount into the page body. Endpoints default to the current
35
+ // origin; override via ?report= and ?relay= for a remote app.
36
+ const params = new URLSearchParams(location.search);
37
+ mountCockpit(document.getElementById("cockpit-root"), {
38
+ reportUrl: params.get("report") ?? undefined,
39
+ relayUrl: params.get("relay") ?? undefined,
40
+ });
41
+ </script>
42
+ </body>
43
+ </html>