@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.
- package/dist/actors.d.ts +2 -2
- package/dist/actors.d.ts.map +1 -1
- package/dist/actors.js +1 -0
- package/dist/broker-routes.d.ts +8 -0
- package/dist/broker-routes.d.ts.map +1 -1
- package/dist/broker-routes.js +15 -0
- package/dist/channel-identity.d.ts +77 -0
- package/dist/channel-identity.d.ts.map +1 -1
- package/dist/channel-identity.js +134 -0
- package/dist/channel-invites.d.ts +179 -0
- package/dist/channel-invites.d.ts.map +1 -0
- package/dist/channel-invites.js +166 -0
- package/dist/channel-reception.d.ts +78 -0
- package/dist/channel-reception.d.ts.map +1 -0
- package/dist/channel-reception.js +219 -0
- package/dist/common.d.ts +1 -1
- package/dist/common.d.ts.map +1 -1
- package/dist/conversation-projection.d.ts +1 -1
- package/dist/conversation-projection.js +1 -1
- package/dist/herdr-workspace-digest.d.ts +151 -0
- package/dist/herdr-workspace-digest.d.ts.map +1 -0
- package/dist/herdr-workspace-digest.js +343 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/machines.d.ts +233 -0
- package/dist/machines.d.ts.map +1 -0
- package/dist/machines.js +550 -0
- package/dist/mesh.d.ts +8 -0
- package/dist/mesh.d.ts.map +1 -1
- package/dist/runtime-catalog-contract.d.ts +1 -1
- package/dist/runtime-catalog-contract.d.ts.map +1 -1
- package/dist/runtime-catalog-contract.js +1 -1
- package/dist/runtime-catalog.generated.d.ts +34 -15
- package/dist/runtime-catalog.generated.d.ts.map +1 -1
- package/dist/runtime-catalog.generated.js +46 -19
- package/dist/runtime-execution.d.ts +1 -1
- package/dist/runtime-execution.d.ts.map +1 -1
- package/dist/scout-delivery.d.ts +1 -0
- package/dist/scout-delivery.d.ts.map +1 -1
- package/dist/terminal-host-topology.d.ts +20 -1
- package/dist/terminal-host-topology.d.ts.map +1 -1
- package/dist/terminal-host-topology.js +8 -2
- package/dist/transports.d.ts +39 -1
- package/dist/transports.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/actors.ts +2 -0
- package/src/broker-routes.ts +17 -0
- package/src/channel-identity.ts +151 -0
- package/src/channel-invites.ts +343 -0
- package/src/channel-reception.ts +308 -0
- package/src/common.ts +1 -0
- package/src/conversation-projection.ts +1 -1
- package/src/herdr-workspace-digest.ts +458 -0
- package/src/index.ts +4 -0
- package/src/machines.ts +743 -0
- package/src/mesh.ts +8 -0
- package/src/runtime-catalog-contract.ts +1 -1
- package/src/runtime-catalog.generated.ts +46 -19
- package/src/runtime-catalog.v1.json +18 -8
- package/src/scout-delivery.ts +1 -0
- package/src/terminal-host-topology.ts +32 -2
- 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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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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"}
|