@openscout/protocol 0.2.98 → 0.2.104

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 (63) hide show
  1. package/dist/actors.d.ts +2 -2
  2. package/dist/actors.d.ts.map +1 -1
  3. package/dist/actors.js +1 -0
  4. package/dist/broker-routes.d.ts +8 -0
  5. package/dist/broker-routes.d.ts.map +1 -1
  6. package/dist/broker-routes.js +15 -0
  7. package/dist/channel-identity.d.ts +77 -0
  8. package/dist/channel-identity.d.ts.map +1 -1
  9. package/dist/channel-identity.js +134 -0
  10. package/dist/channel-invites.d.ts +179 -0
  11. package/dist/channel-invites.d.ts.map +1 -0
  12. package/dist/channel-invites.js +166 -0
  13. package/dist/channel-reception.d.ts +78 -0
  14. package/dist/channel-reception.d.ts.map +1 -0
  15. package/dist/channel-reception.js +219 -0
  16. package/dist/common.d.ts +1 -1
  17. package/dist/common.d.ts.map +1 -1
  18. package/dist/conversation-projection.d.ts +1 -1
  19. package/dist/conversation-projection.js +1 -1
  20. package/dist/herdr-workspace-digest.d.ts +151 -0
  21. package/dist/herdr-workspace-digest.d.ts.map +1 -0
  22. package/dist/herdr-workspace-digest.js +343 -0
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -0
  26. package/dist/machines.d.ts +233 -0
  27. package/dist/machines.d.ts.map +1 -0
  28. package/dist/machines.js +550 -0
  29. package/dist/mesh.d.ts +8 -0
  30. package/dist/mesh.d.ts.map +1 -1
  31. package/dist/runtime-catalog-contract.d.ts +1 -1
  32. package/dist/runtime-catalog-contract.d.ts.map +1 -1
  33. package/dist/runtime-catalog-contract.js +1 -1
  34. package/dist/runtime-catalog.generated.d.ts +34 -15
  35. package/dist/runtime-catalog.generated.d.ts.map +1 -1
  36. package/dist/runtime-catalog.generated.js +46 -19
  37. package/dist/runtime-execution.d.ts +1 -1
  38. package/dist/runtime-execution.d.ts.map +1 -1
  39. package/dist/scout-delivery.d.ts +1 -0
  40. package/dist/scout-delivery.d.ts.map +1 -1
  41. package/dist/terminal-host-topology.d.ts +20 -1
  42. package/dist/terminal-host-topology.d.ts.map +1 -1
  43. package/dist/terminal-host-topology.js +8 -2
  44. package/dist/transports.d.ts +39 -1
  45. package/dist/transports.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/actors.ts +2 -0
  48. package/src/broker-routes.ts +17 -0
  49. package/src/channel-identity.ts +151 -0
  50. package/src/channel-invites.ts +343 -0
  51. package/src/channel-reception.ts +308 -0
  52. package/src/common.ts +1 -0
  53. package/src/conversation-projection.ts +1 -1
  54. package/src/herdr-workspace-digest.ts +458 -0
  55. package/src/index.ts +4 -0
  56. package/src/machines.ts +743 -0
  57. package/src/mesh.ts +8 -0
  58. package/src/runtime-catalog-contract.ts +1 -1
  59. package/src/runtime-catalog.generated.ts +46 -19
  60. package/src/runtime-catalog.v1.json +18 -8
  61. package/src/scout-delivery.ts +1 -0
  62. package/src/terminal-host-topology.ts +32 -2
  63. package/src/transports.ts +48 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Herdr workspace digest — the presentation layer over {@link HerdrSessionTopology}.
3
+ *
4
+ * The raw projection is faithful but shapeless: a complex session is several
5
+ * workspaces × tabs × panes, and handing that whole tree to an operator (or to
6
+ * an assistant that has to answer for it) buries the one thing they came for.
7
+ * A digest ranks, groups, bounds, and describes it.
8
+ *
9
+ * Four commitments, in order of how much they matter:
10
+ *
11
+ * 1. **Attention leads.** A `blocked` pane is the only thing in a herdr session
12
+ * that is actually waiting on a person. It comes first, always, before any
13
+ * inventory. Everything else is context for it.
14
+ * 2. **Directory is the grouping axis, not the tab.** Operators think "which
15
+ * repo", herdr thinks "which tab". Grouping is by the pane's literal working
16
+ * directory — no repo-root inference, because a guessed grouping presented as
17
+ * a fact is worse than an honest fragmented one.
18
+ * 3. **Shape is described, not dumped.** A tab's layout becomes one readable
19
+ * phrase ("2×2 grid", "3 columns (2 · 1 · 1)") derived from the pane rects.
20
+ * 4. **Bounded and honest.** Panes are capped and truncation is declared;
21
+ * `unknown` never collapses into `done`; a persisted projection is labeled
22
+ * last-known rather than passed off as live.
23
+ *
24
+ * There are no mutation verbs here, for the same reason there are none in the
25
+ * topology projection: herdr owns workspaces, tabs and panes, and Scout's layer
26
+ * is coordination over whatever host is present. A digest pane carries the
27
+ * herdr `target` string so a caller can construct a handoff; performing it is
28
+ * the herdr client's job.
29
+ */
30
+ /** Total emitted panes across every group before the digest declares truncation. */
31
+ export const HERDR_DIGEST_PANE_CAP = 40;
32
+ /** Ceiling on each of the two lead lists; attention is a shortlist, not an inventory. */
33
+ export const HERDR_DIGEST_LEAD_CAP = 12;
34
+ const EMPTY_COUNTS = { blocked: 0, working: 0, idle: 0, done: 0, unknown: 0 };
35
+ /** Attention order. Blocked outranks working because it is the only state that waits on a person. */
36
+ const STATUS_RANK = {
37
+ blocked: 5,
38
+ working: 4,
39
+ idle: 3,
40
+ done: 2,
41
+ unknown: 1,
42
+ };
43
+ export function herdrStatusRank(status) {
44
+ return STATUS_RANK[status];
45
+ }
46
+ function emptyCounts() {
47
+ return { ...EMPTY_COUNTS };
48
+ }
49
+ function totalCount(counts) {
50
+ return counts.blocked + counts.working + counts.idle + counts.done + counts.unknown;
51
+ }
52
+ function paneDirectory(pane) {
53
+ return pane.foregroundCwd ?? pane.cwd ?? null;
54
+ }
55
+ function directoryName(directory) {
56
+ if (!directory)
57
+ return "no directory";
58
+ const segments = directory.replace(/\/+$/, "").split("/").filter(Boolean);
59
+ return segments[segments.length - 1] ?? directory;
60
+ }
61
+ function toDigestPane(pane) {
62
+ return {
63
+ target: pane.terminalId ?? pane.paneId ?? null,
64
+ paneId: pane.paneId,
65
+ workspaceId: pane.workspaceId,
66
+ tabId: pane.tabId,
67
+ label: pane.label,
68
+ agent: pane.agent,
69
+ status: pane.agentStatus,
70
+ directory: paneDirectory(pane),
71
+ focused: pane.focused,
72
+ backlog: pane.scroll?.maxOffsetFromBottom ?? null,
73
+ };
74
+ }
75
+ /**
76
+ * Rank panes for a lead list: attention state, then the focused pane (where the
77
+ * operator's eyes already are), then the deepest backlog, then pane id so the
78
+ * order never wobbles between polls.
79
+ */
80
+ function compareLeadPanes(left, right) {
81
+ const byStatus = STATUS_RANK[right.status] - STATUS_RANK[left.status];
82
+ if (byStatus !== 0)
83
+ return byStatus;
84
+ if (left.focused !== right.focused)
85
+ return left.focused ? -1 : 1;
86
+ const byBacklog = (right.backlog ?? -1) - (left.backlog ?? -1);
87
+ if (byBacklog !== 0)
88
+ return byBacklog;
89
+ return left.paneId.localeCompare(right.paneId, undefined, { numeric: true });
90
+ }
91
+ /**
92
+ * Describe a tab's arrangement from its pane rects.
93
+ *
94
+ * Panes sharing an x start a column; panes sharing a y start a row. A layout
95
+ * whose columns all hold the same number of panes is a grid and is named as
96
+ * one; anything else is named by its columns and their depths, which is both
97
+ * shorter and more truthful than inventing a name for an irregular split.
98
+ */
99
+ export function describeHerdrArrangement(layout, paneCount) {
100
+ if (paneCount <= 0)
101
+ return "no panes";
102
+ if (paneCount === 1)
103
+ return "single pane";
104
+ const panes = layout?.panes ?? [];
105
+ if (panes.length !== paneCount)
106
+ return `${paneCount} panes (layout unavailable)`;
107
+ const columns = new Map();
108
+ const rows = new Map();
109
+ for (const pane of panes) {
110
+ columns.set(pane.rect.x, (columns.get(pane.rect.x) ?? 0) + 1);
111
+ rows.set(pane.rect.y, (rows.get(pane.rect.y) ?? 0) + 1);
112
+ }
113
+ if (columns.size === 1)
114
+ return `${paneCount} stacked rows`;
115
+ if (rows.size === 1)
116
+ return `${paneCount} side-by-side columns`;
117
+ const depths = [...columns.entries()]
118
+ .sort((left, right) => left[0] - right[0])
119
+ .map(([, count]) => count);
120
+ const first = depths[0] ?? 0;
121
+ if (depths.every((depth) => depth === first) && first * columns.size === paneCount) {
122
+ return `${first}×${columns.size} grid`;
123
+ }
124
+ return `${columns.size} columns (${depths.join(" · ")})`;
125
+ }
126
+ /**
127
+ * Build the digest for one herdr session topology.
128
+ *
129
+ * Pure: the same topology always digests to the same value, so the whole
130
+ * presentation is testable without a herdr server.
131
+ */
132
+ export function digestHerdrTopology(topology, options = {}) {
133
+ const paneCap = options.paneCap ?? HERDR_DIGEST_PANE_CAP;
134
+ const leadCap = options.leadCap ?? HERDR_DIGEST_LEAD_CAP;
135
+ const counts = emptyCounts();
136
+ const shapes = [];
137
+ const all = [];
138
+ let tabTotal = 0;
139
+ let agentTotal = 0;
140
+ for (const workspace of topology.workspaces) {
141
+ const workspaceLabel = workspace.label ?? (workspace.number != null ? `workspace ${workspace.number}` : workspace.workspaceId);
142
+ for (const tab of workspace.tabs) {
143
+ tabTotal += 1;
144
+ shapes.push({
145
+ workspaceId: workspace.workspaceId,
146
+ workspaceLabel,
147
+ tabId: tab.tabId,
148
+ tabLabel: tab.label ?? (tab.number != null ? `tab ${tab.number}` : tab.tabId),
149
+ paneCount: tab.panes.length,
150
+ arrangement: describeHerdrArrangement(tab.layout, tab.panes.length),
151
+ zoomed: tab.layout?.zoomed ?? false,
152
+ focused: workspace.focused && tab.focused,
153
+ });
154
+ for (const pane of tab.panes) {
155
+ const digestPane = toDigestPane(pane);
156
+ counts[digestPane.status] += 1;
157
+ if (digestPane.agent)
158
+ agentTotal += 1;
159
+ all.push(digestPane);
160
+ }
161
+ }
162
+ }
163
+ // Groups are filled in rank order so the pane cap spends its budget on the
164
+ // panes that matter, not on whichever directory herdr happened to list first.
165
+ const ranked = [...all].sort(compareLeadPanes);
166
+ const kept = ranked.slice(0, paneCap);
167
+ const droppedByDirectory = new Map();
168
+ for (const pane of ranked.slice(paneCap)) {
169
+ const key = pane.directory ?? "";
170
+ let dropped = droppedByDirectory.get(key);
171
+ if (!dropped) {
172
+ dropped = emptyCounts();
173
+ droppedByDirectory.set(key, dropped);
174
+ }
175
+ dropped[pane.status] += 1;
176
+ }
177
+ const groupsByDirectory = new Map();
178
+ for (const pane of all) {
179
+ const key = pane.directory ?? "";
180
+ let group = groupsByDirectory.get(key);
181
+ if (!group) {
182
+ group = {
183
+ directory: pane.directory,
184
+ name: directoryName(pane.directory),
185
+ counts: emptyCounts(),
186
+ panes: [],
187
+ omitted: totalCount(droppedByDirectory.get(key) ?? EMPTY_COUNTS),
188
+ omittedCounts: { ...(droppedByDirectory.get(key) ?? EMPTY_COUNTS) },
189
+ };
190
+ groupsByDirectory.set(key, group);
191
+ }
192
+ group.counts[pane.status] += 1;
193
+ }
194
+ const keptIds = new Set(kept.map((pane) => `${pane.workspaceId}/${pane.paneId}`));
195
+ for (const pane of all) {
196
+ if (!keptIds.has(`${pane.workspaceId}/${pane.paneId}`))
197
+ continue;
198
+ groupsByDirectory.get(pane.directory ?? "")?.panes.push(pane);
199
+ }
200
+ const groups = [...groupsByDirectory.values()];
201
+ for (const group of groups)
202
+ group.panes.sort(compareLeadPanes);
203
+ // Groups sort by what is waiting, then what is moving, then size — the same
204
+ // precedence the lead lists use, applied one level up.
205
+ groups.sort((left, right) => right.counts.blocked - left.counts.blocked
206
+ || right.counts.working - left.counts.working
207
+ || (right.panes.length + right.omitted) - (left.panes.length + left.omitted)
208
+ || left.name.localeCompare(right.name, undefined, { numeric: true }));
209
+ const notes = [];
210
+ if (!topology.running) {
211
+ // "Last known" is only useful with a when: a layout saved an hour ago and
212
+ // one saved in March are the same claim with very different weight.
213
+ const saved = topology.savedAt ? ` Last saved ${new Date(topology.savedAt).toISOString()}.` : "";
214
+ notes.push(topology.unavailable === "unreadable"
215
+ // The session IS running; herdr just would not answer. Saying "stopped"
216
+ // here would be the one thing a digest must never do — state, of live
217
+ // work, that it is not live.
218
+ ? `A herdr server for this session is running but did not answer, so this is the persisted last-known layout, not what is on screen. The usual cause is a protocol skew between the installed herdr client and a long-lived server; restarting that session's server (which exits its pane processes) is what clears it.${saved}`
219
+ : `Not live: this is the persisted last-known layout. Agent status, terminal ids and geometry are absent from it — no pane here is claimed to be running.${saved}`);
220
+ }
221
+ if (counts.unknown > 0) {
222
+ notes.push(`${counts.unknown} ${counts.unknown === 1 ? "pane reports" : "panes report"} unknown: herdr sees an agent it cannot classify. Unknown is the absence of a signal, not completion.`);
223
+ }
224
+ return {
225
+ session: topology.session,
226
+ live: topology.running,
227
+ observedAt: topology.observedAt,
228
+ savedAt: topology.savedAt ?? null,
229
+ unavailable: topology.running ? null : topology.unavailable ?? "not_running",
230
+ totals: {
231
+ workspaces: topology.workspaces.length,
232
+ tabs: tabTotal,
233
+ panes: all.length,
234
+ agents: agentTotal,
235
+ },
236
+ counts,
237
+ needsYou: ranked.filter((pane) => pane.status === "blocked").slice(0, leadCap),
238
+ working: ranked.filter((pane) => pane.status === "working").slice(0, leadCap),
239
+ groups,
240
+ shapes,
241
+ truncated: all.length > paneCap,
242
+ notes,
243
+ };
244
+ }
245
+ /**
246
+ * One row, led by whatever is most specific to this pane.
247
+ *
248
+ * Rows that all begin `term-w1:p1`, `term-w1:p2` share a long prefix, and a
249
+ * scanning reader learns to skip the opening of every line — so the identity
250
+ * column becomes the one column nobody reads. The lead is therefore the pane's
251
+ * label when it has one, and its pane id when it does not, since a short id
252
+ * differs within the first couple of characters where a terminal id does not.
253
+ *
254
+ * The pane id is enough to act on: `herdr agent <verb>` accepts the pane
255
+ * currently hosting an agent as a target. The directory is the basename only —
256
+ * the full path is carried once, in the grouped section, and repeating it on
257
+ * every row buys nothing.
258
+ */
259
+ function paneLine(pane) {
260
+ const parts = pane.label
261
+ ? [pane.label, pane.agent ?? "shell", pane.paneId]
262
+ : [pane.paneId, pane.agent ?? "shell"];
263
+ if (pane.directory)
264
+ parts.push(directoryName(pane.directory));
265
+ if (pane.focused)
266
+ parts.push("focused");
267
+ return ` ${parts.join(" ")}`;
268
+ }
269
+ /** "2 idle not shown" — a collapsed label a reader can decide from. */
270
+ function omittedLabel(counts) {
271
+ const total = totalCount(counts);
272
+ if (total === 0)
273
+ return null;
274
+ const order = ["blocked", "working", "idle", "done", "unknown"];
275
+ const present = order.filter((status) => counts[status] > 0);
276
+ if (present.length === 1)
277
+ return `+${total} ${present[0]} not shown`;
278
+ return `+${total} not shown (${present.map((status) => `${counts[status]} ${status}`).join(" · ")})`;
279
+ }
280
+ function countsLine(counts) {
281
+ const order = ["blocked", "working", "idle", "done", "unknown"];
282
+ const parts = order.filter((status) => counts[status] > 0).map((status) => `${counts[status]} ${status}`);
283
+ return parts.length ? parts.join(" · ") : "no panes";
284
+ }
285
+ /**
286
+ * Render a digest as the text an operator should actually read.
287
+ *
288
+ * Ordering is the whole design: what is waiting, then what is moving, then
289
+ * where things live, then how the screen is arranged. A reader who stops after
290
+ * the first section has still got the answer that mattered.
291
+ *
292
+ * Section heads are verb phrases rather than bare nouns, and there is no
293
+ * trailing caveat block: a qualifier that changes how a count should be read
294
+ * sits directly under that count, because a scanning reader reaches the top of
295
+ * the output and not the bottom of it.
296
+ */
297
+ export function renderHerdrWorkspaceDigest(digest) {
298
+ const lines = [];
299
+ const state = digest.live
300
+ ? "live"
301
+ : digest.unavailable === "unreadable" ? "running, not readable" : "not running";
302
+ const { workspaces, tabs, panes } = digest.totals;
303
+ lines.push(`herdr · ${digest.session} — ${state} · ${workspaces} workspace${workspaces === 1 ? "" : "s"}, ${tabs} tab${tabs === 1 ? "" : "s"}, ${panes} pane${panes === 1 ? "" : "s"} (${countsLine(digest.counts)})`);
304
+ // Caveats sit under the counts they qualify, not in a block at the bottom.
305
+ // A qualifier that changes how a number should be read is body text; a
306
+ // scanning reader never reaches a footnote, and these are the lines that stop
307
+ // someone believing `unknown` meant done or that a stale layout was current.
308
+ for (const note of digest.notes)
309
+ lines.push(` ${note}`);
310
+ if (digest.needsYou.length) {
311
+ lines.push("", "Waiting on you");
312
+ for (const pane of digest.needsYou)
313
+ lines.push(paneLine(pane));
314
+ }
315
+ if (digest.working.length) {
316
+ lines.push("", "Working now");
317
+ for (const pane of digest.working)
318
+ lines.push(paneLine(pane));
319
+ }
320
+ if (digest.groups.length) {
321
+ lines.push("", "Grouped by directory");
322
+ for (const group of digest.groups) {
323
+ const total = totalCount(group.counts);
324
+ const where = group.directory ? ` (${group.directory})` : "";
325
+ const hidden = omittedLabel(group.omittedCounts);
326
+ lines.push(` ${group.name}${where} — ${total} pane${total === 1 ? "" : "s"} · ${countsLine(group.counts)}${hidden ? ` · ${hidden}` : ""}`);
327
+ }
328
+ }
329
+ if (digest.shapes.length) {
330
+ lines.push("", "Arranged like this");
331
+ for (const shape of digest.shapes) {
332
+ const marks = [shape.arrangement];
333
+ if (shape.zoomed)
334
+ marks.push("zoomed");
335
+ if (shape.focused)
336
+ marks.push("focused");
337
+ // The tab id leads because two workspaces can carry the same label, and
338
+ // a label can itself contain the separator — the id cannot do either.
339
+ lines.push(` ${shape.tabId} ${shape.workspaceLabel} / ${shape.tabLabel} — ${marks.join(" · ")}`);
340
+ }
341
+ }
342
+ return lines.join("\n");
343
+ }
package/dist/index.d.ts CHANGED
@@ -7,9 +7,12 @@ export * from "./a2a.js";
7
7
  export * from "./scout-agent-card.js";
8
8
  export * from "./relay-agent-card.js";
9
9
  export * from "./mesh.js";
10
+ export * from "./machines.js";
10
11
  export * from "./conversations.js";
11
12
  export * from "./conversation-projection.js";
12
13
  export * from "./channel-identity.js";
14
+ export * from "./channel-invites.js";
15
+ export * from "./channel-reception.js";
13
16
  export * from "./collaboration.js";
14
17
  export * from "./assigned-roles.js";
15
18
  export * from "./orchestrator-prompt.js";
@@ -53,6 +56,7 @@ export * from "./terminal-surface-id.js";
53
56
  export * from "./terminal-sessions.js";
54
57
  export * from "./terminal-workspaces.js";
55
58
  export * from "./terminal-host-topology.js";
59
+ export * from "./herdr-workspace-digest.js";
56
60
  export * from "./broker-routes.js";
57
61
  export * from "./rendezvous.js";
58
62
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,UAAU,CAAC;AACzB,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,UAAU,CAAC;AACzB,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -7,9 +7,12 @@ export * from "./a2a.js";
7
7
  export * from "./scout-agent-card.js";
8
8
  export * from "./relay-agent-card.js";
9
9
  export * from "./mesh.js";
10
+ export * from "./machines.js";
10
11
  export * from "./conversations.js";
11
12
  export * from "./conversation-projection.js";
12
13
  export * from "./channel-identity.js";
14
+ export * from "./channel-invites.js";
15
+ export * from "./channel-reception.js";
13
16
  export * from "./collaboration.js";
14
17
  export * from "./assigned-roles.js";
15
18
  export * from "./orchestrator-prompt.js";
@@ -53,5 +56,6 @@ export * from "./terminal-surface-id.js";
53
56
  export * from "./terminal-sessions.js";
54
57
  export * from "./terminal-workspaces.js";
55
58
  export * from "./terminal-host-topology.js";
59
+ export * from "./herdr-workspace-digest.js";
56
60
  export * from "./broker-routes.js";
57
61
  export * from "./rendezvous.js";
@@ -0,0 +1,233 @@
1
+ import type { AdvertiseScope, MetadataMap, ScoutId } from "./common.js";
2
+ /**
3
+ * Machines — the physical (or virtual) boxes Scout knows about.
4
+ *
5
+ * A machine is NOT a node. A node is a Scout broker runtime; a machine is the
6
+ * computer it runs on. The distinction matters because most machines Scout can
7
+ * see are not running Scout at all: a tailnet peer, a Mac advertising itself on
8
+ * the LAN, a Linux box that only answers ARP. Modelling them as nodes would
9
+ * either invent fake nodes or make them invisible, and both were wrong.
10
+ *
11
+ * A machine record is assembled from **evidence**, never asserted. Four
12
+ * channels report independently and none of them is authoritative:
13
+ *
14
+ * | channel | tells us |
15
+ * | --------- | ----------------------------------------------- |
16
+ * | `scout` | a broker node lives here (id, mesh, urls) |
17
+ * | `tailnet` | reachable over Tailscale (stable peer id, IPs) |
18
+ * | `lan` | seen on this network (mDNS advert, ARP neighbor)|
19
+ * | `host` | terminal hosts present (herdr, tmux, zellij) |
20
+ *
21
+ * The join between them is `machineIdentityKeys` + `groupMachineEvidence`.
22
+ * Everything else in this module is a pure derivation over a group, so the
23
+ * whole model is testable without a network, a broker, or a database.
24
+ */
25
+ export declare const MACHINE_ID_PREFIX = "mach-";
26
+ export type MachineEvidenceKind = "scout" | "tailnet" | "lan" | "host";
27
+ export type MachinePlatform = "macos" | "linux" | "windows" | "ios" | "android" | "unknown";
28
+ /**
29
+ * What a machine can do *for Scout*, as observed — never as configured. A
30
+ * capability is present because something answered, so an operator reading
31
+ * `herdr` on a row knows a herdr probe actually succeeded there.
32
+ */
33
+ export type MachineCapability = "scout-broker" | "scout-web" | "scout-pairing" | "herdr" | "tmux" | "zellij" | "ssh" | "vnc" | "smb" | "http";
34
+ export type MachineRouteKind = "loopback" | "lan" | "tailnet" | "mesh" | "relay";
35
+ /** One way to reach a machine. Ordered by preference in `deriveMachineRoutes`. */
36
+ export interface MachineRoute {
37
+ kind: MachineRouteKind;
38
+ host: string;
39
+ port?: number;
40
+ url?: string;
41
+ lastSeenAt?: number;
42
+ }
43
+ export type MachineTerminalHost = "herdr" | "tmux" | "zellij";
44
+ export type LanDiscoveryMethod = "mdns" | "arp";
45
+ interface MachineEvidenceBase {
46
+ kind: MachineEvidenceKind;
47
+ observedAt: number;
48
+ }
49
+ /** A Scout broker node registered with, or discovered by, this broker. */
50
+ export interface ScoutMachineEvidence extends MachineEvidenceBase {
51
+ kind: "scout";
52
+ nodeId: ScoutId;
53
+ meshId?: ScoutId;
54
+ nodeName: string;
55
+ hostName?: string;
56
+ brokerUrl?: string;
57
+ webUrl?: string;
58
+ /** The node's named web doorway (`m1.scout.local`) — see NodeDefinition.webHost. */
59
+ webHost?: string;
60
+ advertiseScope?: AdvertiseScope;
61
+ tailnetName?: string;
62
+ isSelf?: boolean;
63
+ }
64
+ /** A `tailscale status` peer (or self). Present whether or not it runs Scout. */
65
+ export interface TailnetMachineEvidence extends MachineEvidenceBase {
66
+ kind: "tailnet";
67
+ peerId: string;
68
+ hostName?: string;
69
+ dnsName?: string;
70
+ tailnetName?: string;
71
+ addresses: string[];
72
+ online: boolean;
73
+ os?: string;
74
+ tags?: string[];
75
+ isSelf?: boolean;
76
+ }
77
+ /** A local-network sighting: an mDNS service instance or an ARP neighbor. */
78
+ export interface LanMachineEvidence extends MachineEvidenceBase {
79
+ kind: "lan";
80
+ method: LanDiscoveryMethod;
81
+ hostName?: string;
82
+ instanceName?: string;
83
+ /** Full service type as browsed, e.g. `_openscout._tcp`. */
84
+ serviceType?: string;
85
+ addresses: string[];
86
+ port?: number;
87
+ macAddress?: string;
88
+ /** Best-effort OUI vendor. Null whenever the prefix is not in the table. */
89
+ vendor?: string;
90
+ interfaceName?: string;
91
+ txt?: MetadataMap;
92
+ }
93
+ /** A terminal host observed on a machine Scout can probe. */
94
+ export interface HostMachineEvidence extends MachineEvidenceBase {
95
+ kind: "host";
96
+ host: MachineTerminalHost;
97
+ /** Machine this was probed on — host probes are never self-identifying. */
98
+ hostName?: string;
99
+ nodeId?: ScoutId;
100
+ sessionCount: number;
101
+ runningSessionCount?: number;
102
+ version?: string;
103
+ }
104
+ export type MachineEvidence = ScoutMachineEvidence | TailnetMachineEvidence | LanMachineEvidence | HostMachineEvidence;
105
+ /**
106
+ * Identity keys, split by how long they stay true.
107
+ *
108
+ * `durable` keys name the machine itself — a Tailscale node id, a MAC, a Scout
109
+ * node id, a hostname. They are persisted with the record and re-join evidence
110
+ * across passes, days apart.
111
+ *
112
+ * `transient` keys are only true right now. A LAN IP is the important one: it
113
+ * is what joins an ARP row to an mDNS advert *inside one scan*, and it is
114
+ * exactly what DHCP hands to a different machine next week. Persisting it
115
+ * would silently fuse two machines, so it never leaves the pass that saw it.
116
+ */
117
+ export interface MachineIdentityKeys {
118
+ durable: string[];
119
+ transient: string[];
120
+ }
121
+ /** mDNS service types Scout browses, and what each one proves. */
122
+ export declare const MACHINE_SERVICE_CAPABILITIES: Readonly<Record<string, MachineCapability>>;
123
+ /** Browsed for inventory. `_workstation._tcp` proves nothing but presence. */
124
+ export declare const MACHINE_LAN_SERVICE_TYPES: readonly string[];
125
+ export declare function normalizeMachineHostName(value: string | null | undefined): string;
126
+ /**
127
+ * Lowercase colon-form MAC. Returns "" for entries that identify nothing —
128
+ * broadcast, all-zero, or an incomplete ARP row.
129
+ *
130
+ * BSD `arp` drops leading zeros (`3c:22:fb:1:2:3`), so octets are padded
131
+ * rather than the string being scrubbed of separators: scrubbing turns that
132
+ * nine-character form into a rejected MAC instead of a valid one.
133
+ */
134
+ export declare function normalizeMacAddress(value: string | null | undefined): string;
135
+ export declare function normalizeMachineAddress(value: string | null | undefined): string;
136
+ export declare function isLoopbackAddress(value: string): boolean;
137
+ /** Tailscale's CGNAT range (100.64.0.0/10) and its IPv6 ULA prefix. */
138
+ export declare function isTailnetAddress(value: string): boolean;
139
+ /**
140
+ * The keys one piece of evidence claims. Two pieces of evidence describe the
141
+ * same machine when they share any key — see `groupMachineEvidence`.
142
+ */
143
+ export declare function machineIdentityKeys(evidence: MachineEvidence): MachineIdentityKeys;
144
+ export interface MachineEvidenceGroup {
145
+ /** Durable keys only — what gets persisted so the next pass re-joins here. */
146
+ identityKeys: string[];
147
+ evidence: MachineEvidence[];
148
+ /**
149
+ * Indices of every seed this group attached to, ascending. More than one
150
+ * means fresh evidence proved two records were always the same machine —
151
+ * the first is the survivor, the rest are absorbed by the caller.
152
+ */
153
+ seedIndices: number[];
154
+ }
155
+ /**
156
+ * Union-find over identity keys. Evidence lands in the same group when it
157
+ * shares any key, directly or through a chain — an ARP row and an mDNS advert
158
+ * share an IP; the advert and a tailnet peer share a hostname; so all three
159
+ * describe one machine even though ARP and Tailscale share nothing at all.
160
+ *
161
+ * `seeds` are the durable key sets of machines already on record. They enter
162
+ * the union as members, which is what lets a machine seen only by ARP today
163
+ * and only by Tailscale tomorrow stay one record: the seed bridges the passes.
164
+ */
165
+ export declare function groupMachineEvidence(evidence: readonly MachineEvidence[], seeds?: readonly (readonly string[])[]): MachineEvidenceGroup[];
166
+ /**
167
+ * Deterministic id from a group's durable keys, so two brokers observing the
168
+ * same machine mint the same id and a restart never renumbers the fleet.
169
+ * FNV-1a ×2, matching `stableChannelId`.
170
+ */
171
+ export declare function stableMachineId(identityKeys: readonly string[]): ScoutId;
172
+ /**
173
+ * The name to show. Scout's own name for the node wins because the operator
174
+ * chose it; then the tailnet hostname, then whatever the LAN advertised. A
175
+ * machine known only by MAC gets its MAC, never a fabricated label.
176
+ */
177
+ export declare function deriveMachineName(evidence: readonly MachineEvidence[]): string;
178
+ export declare function deriveMachinePlatform(evidence: readonly MachineEvidence[]): MachinePlatform;
179
+ export declare function deriveMachineCapabilities(evidence: readonly MachineEvidence[]): MachineCapability[];
180
+ /**
181
+ * Routes, best first: loopback (it's this machine), then LAN (no hop), then
182
+ * tailnet, then whatever the mesh advertised. This is the order a caller
183
+ * should dial, and it mirrors the LAN-first preference iOS already uses.
184
+ */
185
+ /**
186
+ * Every way we know to reach this machine, best first. `isSelf` decides whether
187
+ * loopback belongs at all: a peer advertising `http://127.0.0.1:43120` is
188
+ * describing its own loopback, which is not a route to it from here.
189
+ */
190
+ export declare function deriveMachineRoutes(evidence: readonly MachineEvidence[], isSelf?: boolean): MachineRoute[];
191
+ export type MachinePresence = "online" | "recent" | "offline";
192
+ /** Anything seen inside this window counts as present. */
193
+ export declare const MACHINE_ONLINE_WINDOW_MS: number;
194
+ export declare const MACHINE_RECENT_WINDOW_MS: number;
195
+ export interface MachineRecord {
196
+ id: ScoutId;
197
+ /** Operator-set name. Null means "use `name`". */
198
+ displayName: string | null;
199
+ /** Derived from the strongest evidence — recomputed every pass. */
200
+ name: string;
201
+ platform: MachinePlatform;
202
+ /** Durable keys, sorted. The join key set for the next pass. */
203
+ identityKeys: string[];
204
+ isSelf: boolean;
205
+ scoutNodeId?: ScoutId;
206
+ meshId?: ScoutId;
207
+ tailnetId?: string;
208
+ tailnetName?: string;
209
+ hostNames: string[];
210
+ addresses: string[];
211
+ macAddresses: string[];
212
+ capabilities: MachineCapability[];
213
+ routes: MachineRoute[];
214
+ evidence: MachineEvidence[];
215
+ /** Operator marked it worth keeping even when nothing has seen it in weeks. */
216
+ pinned: boolean;
217
+ notes?: string;
218
+ firstSeenAt: number;
219
+ lastSeenAt: number;
220
+ metadata?: MetadataMap;
221
+ }
222
+ export declare function machineLabel(machine: Pick<MachineRecord, "displayName" | "name">): string;
223
+ export declare function machinePresence(machine: Pick<MachineRecord, "lastSeenAt">, now?: number): MachinePresence;
224
+ /**
225
+ * Build (or rebuild) a record from a group. `previous` carries the parts an
226
+ * operator owns — id, chosen name, notes, pin, first-seen — which no amount of
227
+ * fresh evidence may overwrite.
228
+ */
229
+ export declare function buildMachineRecord(group: MachineEvidenceGroup, previous?: Partial<MachineRecord> | null): MachineRecord;
230
+ /** Sort for display: this machine, then present ones, then by name. */
231
+ export declare function compareMachines(a: MachineRecord, b: MachineRecord): number;
232
+ export {};
233
+ //# sourceMappingURL=machines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machines.d.ts","sourceRoot":"","sources":["../src/machines.ts"],"names":[],"mappings":"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"}