borgmcp 1.0.6 → 1.0.8

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 (160) hide show
  1. package/README.md +5 -3
  2. package/dist/assimilate-cmd.js +39 -511
  3. package/dist/assimilate-deps.js +3 -177
  4. package/dist/assimilate-welcome.js +2 -24
  5. package/dist/auth-env.js +1 -107
  6. package/dist/auth.js +23 -612
  7. package/dist/claude.js +11 -281
  8. package/dist/cli-help.js +29 -50
  9. package/dist/cli-platform.js +4 -94
  10. package/dist/codex-app-server.js +4 -228
  11. package/dist/codex-app-wake.js +2 -122
  12. package/dist/codex-launch.js +1 -81
  13. package/dist/codex-remote.js +1 -250
  14. package/dist/config-utils.js +3 -385
  15. package/dist/config.js +1 -190
  16. package/dist/console-prefix.js +1 -86
  17. package/dist/cube-name.js +1 -65
  18. package/dist/cubes.js +4 -269
  19. package/dist/debug.js +1 -71
  20. package/dist/device-auth.js +1 -167
  21. package/dist/direct-log.js +1 -11
  22. package/dist/health-beat.js +1 -168
  23. package/dist/inbox-monitor.js +1 -129
  24. package/dist/index.js +26 -1378
  25. package/dist/lifecycle-log-guard.js +2 -93
  26. package/dist/list-roles-render.js +6 -39
  27. package/dist/log-audit.js +3 -186
  28. package/dist/log-stream.js +9 -848
  29. package/dist/name-validator.js +1 -22
  30. package/dist/parse-assimilate-args.js +1 -82
  31. package/dist/postinstall.js +8 -22
  32. package/dist/regen-format.js +11 -337
  33. package/dist/regen.js +5 -83
  34. package/dist/remote-client.d.ts +4 -7
  35. package/dist/remote-client.js +1 -695
  36. package/dist/role-resolver.js +1 -36
  37. package/dist/role-section.js +8 -208
  38. package/dist/roster-render.js +3 -96
  39. package/dist/setup.js +41 -251
  40. package/dist/shell-escape.js +1 -22
  41. package/dist/spawn.js +10 -29
  42. package/dist/stale-version-check.js +1 -102
  43. package/dist/stream-owner.js +2 -202
  44. package/dist/stream-status.js +3 -211
  45. package/dist/subscription-retry.js +1 -23
  46. package/dist/sync-roles-render.js +3 -118
  47. package/dist/sync.js +22 -286
  48. package/dist/templates.js +120 -626
  49. package/dist/terminal-title.js +1 -68
  50. package/dist/token-crypto.js +1 -91
  51. package/dist/token-store.js +1 -222
  52. package/dist/types.d.ts +0 -5
  53. package/dist/types.js +0 -5
  54. package/dist/version.js +2 -78
  55. package/dist/worktree-lifecycle.js +2 -173
  56. package/package.json +12 -2
  57. package/dist/assimilate-cmd.d.ts.map +0 -1
  58. package/dist/assimilate-cmd.js.map +0 -1
  59. package/dist/assimilate-deps.d.ts.map +0 -1
  60. package/dist/assimilate-deps.js.map +0 -1
  61. package/dist/assimilate-welcome.d.ts.map +0 -1
  62. package/dist/assimilate-welcome.js.map +0 -1
  63. package/dist/auth-env.d.ts.map +0 -1
  64. package/dist/auth-env.js.map +0 -1
  65. package/dist/auth.d.ts.map +0 -1
  66. package/dist/auth.js.map +0 -1
  67. package/dist/claude.d.ts.map +0 -1
  68. package/dist/claude.js.map +0 -1
  69. package/dist/cli-help.d.ts.map +0 -1
  70. package/dist/cli-help.js.map +0 -1
  71. package/dist/cli-platform.d.ts.map +0 -1
  72. package/dist/cli-platform.js.map +0 -1
  73. package/dist/codex-app-server.d.ts.map +0 -1
  74. package/dist/codex-app-server.js.map +0 -1
  75. package/dist/codex-app-wake.d.ts.map +0 -1
  76. package/dist/codex-app-wake.js.map +0 -1
  77. package/dist/codex-launch.d.ts.map +0 -1
  78. package/dist/codex-launch.js.map +0 -1
  79. package/dist/codex-remote.d.ts.map +0 -1
  80. package/dist/codex-remote.js.map +0 -1
  81. package/dist/config-utils.d.ts.map +0 -1
  82. package/dist/config-utils.js.map +0 -1
  83. package/dist/config.d.ts.map +0 -1
  84. package/dist/config.js.map +0 -1
  85. package/dist/console-prefix.d.ts.map +0 -1
  86. package/dist/console-prefix.js.map +0 -1
  87. package/dist/cube-name.d.ts.map +0 -1
  88. package/dist/cube-name.js.map +0 -1
  89. package/dist/cubes.d.ts.map +0 -1
  90. package/dist/cubes.js.map +0 -1
  91. package/dist/debug.d.ts.map +0 -1
  92. package/dist/debug.js.map +0 -1
  93. package/dist/device-auth.d.ts.map +0 -1
  94. package/dist/device-auth.js.map +0 -1
  95. package/dist/direct-log.d.ts.map +0 -1
  96. package/dist/direct-log.js.map +0 -1
  97. package/dist/health-beat.d.ts.map +0 -1
  98. package/dist/health-beat.js.map +0 -1
  99. package/dist/inbox-monitor.d.ts.map +0 -1
  100. package/dist/inbox-monitor.js.map +0 -1
  101. package/dist/index.d.ts.map +0 -1
  102. package/dist/index.js.map +0 -1
  103. package/dist/lifecycle-log-guard.d.ts.map +0 -1
  104. package/dist/lifecycle-log-guard.js.map +0 -1
  105. package/dist/list-roles-render.d.ts.map +0 -1
  106. package/dist/list-roles-render.js.map +0 -1
  107. package/dist/log-audit.d.ts.map +0 -1
  108. package/dist/log-audit.js.map +0 -1
  109. package/dist/log-stream.d.ts.map +0 -1
  110. package/dist/log-stream.js.map +0 -1
  111. package/dist/name-validator.d.ts.map +0 -1
  112. package/dist/name-validator.js.map +0 -1
  113. package/dist/parse-assimilate-args.d.ts.map +0 -1
  114. package/dist/parse-assimilate-args.js.map +0 -1
  115. package/dist/postinstall.d.ts.map +0 -1
  116. package/dist/postinstall.js.map +0 -1
  117. package/dist/regen-format.d.ts.map +0 -1
  118. package/dist/regen-format.js.map +0 -1
  119. package/dist/regen.d.ts.map +0 -1
  120. package/dist/regen.js.map +0 -1
  121. package/dist/remote-client.d.ts.map +0 -1
  122. package/dist/remote-client.js.map +0 -1
  123. package/dist/role-resolver.d.ts.map +0 -1
  124. package/dist/role-resolver.js.map +0 -1
  125. package/dist/role-section.d.ts.map +0 -1
  126. package/dist/role-section.js.map +0 -1
  127. package/dist/roster-render.d.ts.map +0 -1
  128. package/dist/roster-render.js.map +0 -1
  129. package/dist/setup.d.ts.map +0 -1
  130. package/dist/setup.js.map +0 -1
  131. package/dist/shell-escape.d.ts.map +0 -1
  132. package/dist/shell-escape.js.map +0 -1
  133. package/dist/spawn.d.ts.map +0 -1
  134. package/dist/spawn.js.map +0 -1
  135. package/dist/stale-version-check.d.ts.map +0 -1
  136. package/dist/stale-version-check.js.map +0 -1
  137. package/dist/stream-owner.d.ts.map +0 -1
  138. package/dist/stream-owner.js.map +0 -1
  139. package/dist/stream-status.d.ts.map +0 -1
  140. package/dist/stream-status.js.map +0 -1
  141. package/dist/subscription-retry.d.ts.map +0 -1
  142. package/dist/subscription-retry.js.map +0 -1
  143. package/dist/sync-roles-render.d.ts.map +0 -1
  144. package/dist/sync-roles-render.js.map +0 -1
  145. package/dist/sync.d.ts.map +0 -1
  146. package/dist/sync.js.map +0 -1
  147. package/dist/templates.d.ts.map +0 -1
  148. package/dist/templates.js.map +0 -1
  149. package/dist/terminal-title.d.ts.map +0 -1
  150. package/dist/terminal-title.js.map +0 -1
  151. package/dist/token-crypto.d.ts.map +0 -1
  152. package/dist/token-crypto.js.map +0 -1
  153. package/dist/token-store.d.ts.map +0 -1
  154. package/dist/token-store.js.map +0 -1
  155. package/dist/types.d.ts.map +0 -1
  156. package/dist/types.js.map +0 -1
  157. package/dist/version.d.ts.map +0 -1
  158. package/dist/version.js.map +0 -1
  159. package/dist/worktree-lifecycle.d.ts.map +0 -1
  160. package/dist/worktree-lifecycle.js.map +0 -1
@@ -1,36 +1 @@
1
- /**
2
- * Normalize a role-name argument or stored name into a slug used for
3
- * both worktree-path construction and role lookup. Single source of
4
- * truth so the path and the matched role always agree.
5
- *
6
- * Path-safe: strips characters outside `[a-z0-9-]` after the
7
- * underscore/whitespace collapse. The no-arg path bypasses
8
- * validateName (the matched role's name comes from the DB, which
9
- * has no charset constraint on role names — CreateRoleSchema is
10
- * `min(1).max(64)` only), so the safety has to live here.
11
- */
12
- export function roleSlug(raw) {
13
- return raw.toLowerCase().replace(/[_\s]+/g, '-').replace(/[^a-z0-9-]/g, '');
14
- }
15
- /**
16
- * Case- and separator-insensitive lookup against the cube's roles.
17
- */
18
- export function matchRoleByName(roles, query) {
19
- const slug = roleSlug(query);
20
- return roles.find((r) => roleSlug(r.name) === slug);
21
- }
22
- /**
23
- * Default-role picker for the no-arg case:
24
- * - First drone in cube + a human-seat role exists → that role
25
- * - Otherwise → the is_default role
26
- * - Neither available → undefined (caller errors out)
27
- */
28
- export function pickDefaultRole(roles, opts) {
29
- if (opts.isFirstDrone) {
30
- const humanSeat = roles.find((r) => r.is_human_seat);
31
- if (humanSeat)
32
- return humanSeat;
33
- }
34
- return roles.find((r) => r.is_default);
35
- }
36
- //# sourceMappingURL=role-resolver.js.map
1
+ function a(e){return e.toLowerCase().replace(/[_\s]+/g,"-").replace(/[^a-z0-9-]/g,"")}function i(e,t){const n=a(t);return e.find(r=>a(r.name)===n)}function o(e,t){if(t.isFirstDrone){const n=e.find(r=>r.is_human_seat);if(n)return n}return e.find(n=>n.is_default)}export{i as matchRoleByName,o as pickDefaultRole,a as roleSlug};
@@ -1,208 +1,8 @@
1
- // gh#496-A(b) + gh#513 — SINGLE canonical role-section parser, shared by BOTH
2
- // the client and the worker (the prior two-copy drift is eliminated). The
3
- // client render compression (`regen-format.ts`) splits role text with this
4
- // parser to decide which `… rationale:` sections to stub; the worker's
5
- // `getRoleRationale` (cubes.ts) resolves those stub headings and `sync-plan.ts`
6
- // diffs role sections — all import THIS module, so heading derivation
7
- // (sans-colon, isLabelLine rules) is identical by construction (no parity
8
- // test needed; agreement is structural).
9
- //
10
- // The worker imports this via `../client/src/role-section` (mirrors the
11
- // existing `workers/mcp-server.ts → ../client/src/templates` precedent). KEEP
12
- // THIS MODULE SELF-CONTAINED (no runtime imports) so the worker bundle
13
- // resolves it cleanly; the client tsconfig `rootDir: ./src` also requires the
14
- // shared canonical to live under `client/src/`.
15
- /**
16
- * Test whether a single line is a plain-label section heading.
17
- *
18
- * Rules (deliberately narrow to avoid false positives against prose
19
- * that merely contains a colon):
20
- * - no leading whitespace (label lines start at column 0);
21
- * - the line ends with exactly one `:`;
22
- * - the text before the colon is non-empty, contains no other `:`,
23
- * and is "short" (≤ 60 chars) — long colon-terminated prose lines
24
- * are not labels;
25
- * - the label is not a markdown emphasis run (does not start with
26
- * `*`, `-`, `#`, `>` or backtick) — those are list/markdown lines,
27
- * not plain labels (the woven `**Dense communication discipline:**`
28
- * constant headings must NOT be treated as section boundaries).
29
- */
30
- export function isLabelLine(line) {
31
- // Leading whitespace disqualifies (label lines are flush-left).
32
- if (/^\s/.test(line))
33
- return false;
34
- if (!line.endsWith(':'))
35
- return false;
36
- const label = line.slice(0, -1);
37
- if (label.length === 0)
38
- return false;
39
- if (label.length > 60)
40
- return false;
41
- // Exactly one colon total (the trailing one).
42
- if (label.includes(':'))
43
- return false;
44
- // Markdown/list/quote/code lead-ins are not plain labels.
45
- if (/^[*\-#>`]/.test(label))
46
- return false;
47
- return true;
48
- }
49
- /**
50
- * Parse role `detailed_description` text into ordered sections.
51
- *
52
- * The result always round-trips byte-identical through
53
- * `serializeSections`. An empty string yields a single empty preamble
54
- * section so callers always have a stable shape to address.
55
- */
56
- export function parseRoleSections(text) {
57
- const sections = [];
58
- const lines = text.split('\n');
59
- // Reconstruct each line WITH its terminating newline so that the
60
- // concatenation is lossless. `split('\n')` drops the separators; we
61
- // re-append '\n' to every line except the last (which had no trailing
62
- // newline in the source unless the source ended with one — in which
63
- // case the final split element is '' and contributes nothing).
64
- const lineWithSep = (idx) => idx < lines.length - 1 ? lines[idx] + '\n' : lines[idx];
65
- let currentHeading = null;
66
- let currentKind = 'preamble';
67
- let currentBody = '';
68
- let started = false;
69
- const flush = () => {
70
- sections.push({ heading: currentHeading, kind: currentKind, body: currentBody });
71
- };
72
- for (let i = 0; i < lines.length; i++) {
73
- const raw = lines[i];
74
- if (isLabelLine(raw)) {
75
- // Close the in-progress section (preamble or previous label) and
76
- // open a new label section headed by this line.
77
- flush();
78
- currentHeading = raw.slice(0, -1).trim();
79
- currentKind = 'label';
80
- currentBody = lineWithSep(i);
81
- started = true;
82
- }
83
- else {
84
- currentBody += lineWithSep(i);
85
- started = true;
86
- }
87
- }
88
- // Always emit the final in-progress section. For empty input this is a
89
- // single empty preamble (started === false, currentBody === '').
90
- if (started || sections.length === 0) {
91
- flush();
92
- }
93
- return sections;
94
- }
95
- /** Concatenate sections back into the original field text. */
96
- export function serializeSections(sections) {
97
- return sections.map((s) => s.body).join('');
98
- }
99
- function normalizeHeading(value) {
100
- return value.trim().toLowerCase();
101
- }
102
- /**
103
- * Build the body for a freshly inserted/replaced label section from a
104
- * heading + caller-supplied body text.
105
- *
106
- * The heading line is rendered as `<heading>:` on its own line. The
107
- * caller's `body` is the text BELOW the heading. We guarantee the
108
- * section body ends with a single trailing newline so adjacent sections
109
- * stay separated when re-serialized (matching how real role text
110
- * separates label sections with blank lines is the caller's choice via
111
- * their body text; we only ensure the structural newline).
112
- */
113
- function renderLabelSection(heading, body) {
114
- const headingLine = `${heading.trim()}:\n`;
115
- if (body === '')
116
- return headingLine;
117
- // Ensure the body ends with a newline so the next section's heading
118
- // starts on its own line.
119
- const normalizedBody = body.endsWith('\n') ? body : body + '\n';
120
- return headingLine + normalizedBody;
121
- }
122
- /**
123
- * Guarantee the section at `idx` ends in a newline before a new section
124
- * heading is glued on after it.
125
- *
126
- * Real role `detailed_description` text (every template role) ends WITHOUT
127
- * a trailing newline — it closes on the woven `${...DISCIPLINE}` constant.
128
- * Appending a `<heading>:` line directly onto such text would fuse the
129
- * heading onto the prior section's last line, so the heading is no longer
130
- * a flush-left label and the inserted section is LOST on re-parse. Adding
131
- * the structural newline here keeps the inserted heading on its own line.
132
- *
133
- * No-ops when the preceding body is empty (an empty preamble needs no
134
- * separator) or already ends in '\n' (preserves the already-correct case
135
- * byte-for-byte).
136
- */
137
- function ensureTrailingNewline(sections, idx) {
138
- if (idx < 0 || idx >= sections.length)
139
- return;
140
- const prev = sections[idx];
141
- if (prev.body !== '' && !prev.body.endsWith('\n')) {
142
- sections[idx] = { ...prev, body: prev.body + '\n' };
143
- }
144
- }
145
- /**
146
- * Apply a single section-level patch to role text, returning the new
147
- * full field text. Every other byte is preserved.
148
- *
149
- * - replace: overwrite the matched section's body with a re-rendered
150
- * `<heading>:\n<body>` block. The heading match is case-insensitive
151
- * on the label; the re-rendered heading uses the supplied casing.
152
- * - insert: add a new label section. Placed after the section named by
153
- * `after` (case-insensitive), or appended at the end when `after` is
154
- * omitted/null. Rejects if a section with the same heading already
155
- * exists (use replace instead).
156
- * - delete: drop the matched section entirely.
157
- *
158
- * Throws when the target section is missing (replace/delete) or already
159
- * present (insert), or when `after` does not resolve.
160
- */
161
- export function patchRoleSectionText(text, op) {
162
- const sections = parseRoleSections(text);
163
- const targetKey = normalizeHeading(op.heading);
164
- if (op.action === 'replace') {
165
- const idx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
166
- if (idx === -1) {
167
- throw new Error(`Role section "${op.heading}" not found. Use action="insert" to add it.`);
168
- }
169
- sections[idx] = {
170
- heading: op.heading.trim(),
171
- kind: 'label',
172
- body: renderLabelSection(op.heading, op.body),
173
- };
174
- return serializeSections(sections);
175
- }
176
- if (op.action === 'delete') {
177
- const idx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
178
- if (idx === -1) {
179
- throw new Error(`Role section "${op.heading}" not found.`);
180
- }
181
- sections.splice(idx, 1);
182
- return serializeSections(sections);
183
- }
184
- // insert
185
- const exists = sections.some((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === targetKey);
186
- if (exists) {
187
- throw new Error(`Role section "${op.heading}" already exists. Use action="replace" to overwrite it.`);
188
- }
189
- const newSection = {
190
- heading: op.heading.trim(),
191
- kind: 'label',
192
- body: renderLabelSection(op.heading, op.body),
193
- };
194
- if (op.after == null) {
195
- ensureTrailingNewline(sections, sections.length - 1);
196
- sections.push(newSection);
197
- return serializeSections(sections);
198
- }
199
- const afterKey = normalizeHeading(op.after);
200
- const afterIdx = sections.findIndex((s) => s.kind === 'label' && s.heading != null && normalizeHeading(s.heading) === afterKey);
201
- if (afterIdx === -1) {
202
- throw new Error(`Cannot insert after section "${op.after}" — it does not exist.`);
203
- }
204
- ensureTrailingNewline(sections, afterIdx);
205
- sections.splice(afterIdx + 1, 0, newSection);
206
- return serializeSections(sections);
207
- }
208
- //# sourceMappingURL=role-section.js.map
1
+ function b(t){if(/^\s/.test(t)||!t.endsWith(":"))return!1;const e=t.slice(0,-1);return!(e.length===0||e.length>60||e.includes(":")||/^[*\-#>`]/.test(e))}function w(t){const e=[],n=t.split(`
2
+ `),l=i=>i<n.length-1?n[i]+`
3
+ `:n[i];let f=null,s="preamble",d="",a=!1;const r=()=>{e.push({heading:f,kind:s,body:d})};for(let i=0;i<n.length;i++){const u=n[i];b(u)?(r(),f=u.slice(0,-1).trim(),s="label",d=l(i),a=!0):(d+=l(i),a=!0)}return(a||e.length===0)&&r(),e}function c(t){return t.map(e=>e.body).join("")}function o(t){return t.trim().toLowerCase()}function h(t,e){const n=`${t.trim()}:
4
+ `;if(e==="")return n;const l=e.endsWith(`
5
+ `)?e:e+`
6
+ `;return n+l}function g(t,e){if(e<0||e>=t.length)return;const n=t[e];n.body!==""&&!n.body.endsWith(`
7
+ `)&&(t[e]={...n,body:n.body+`
8
+ `})}function y(t,e){const n=w(t),l=o(e.heading);if(e.action==="replace"){const r=n.findIndex(i=>i.kind==="label"&&i.heading!=null&&o(i.heading)===l);if(r===-1)throw new Error(`Role section "${e.heading}" not found. Use action="insert" to add it.`);return n[r]={heading:e.heading.trim(),kind:"label",body:h(e.heading,e.body)},c(n)}if(e.action==="delete"){const r=n.findIndex(i=>i.kind==="label"&&i.heading!=null&&o(i.heading)===l);if(r===-1)throw new Error(`Role section "${e.heading}" not found.`);return n.splice(r,1),c(n)}if(n.some(r=>r.kind==="label"&&r.heading!=null&&o(r.heading)===l))throw new Error(`Role section "${e.heading}" already exists. Use action="replace" to overwrite it.`);const s={heading:e.heading.trim(),kind:"label",body:h(e.heading,e.body)};if(e.after==null)return g(n,n.length-1),n.push(s),c(n);const d=o(e.after),a=n.findIndex(r=>r.kind==="label"&&r.heading!=null&&o(r.heading)===d);if(a===-1)throw new Error(`Cannot insert after section "${e.after}" \u2014 it does not exist.`);return g(n,a),n.splice(a+1,0,s),c(n)}export{b as isLabelLine,w as parseRoleSections,y as patchRoleSectionText,c as serializeSections};
@@ -1,96 +1,3 @@
1
- /**
2
- * Pure renderer for the `borg:roster` MCP tool output.
3
- *
4
- * Split out from `index.ts` so the T2.1 sender-side liveness probe
5
- * (the `awake`/`stale-since-X` column rendering when a `since` arg is
6
- * passed) can be unit-tested without spinning up the MCP server. Same
7
- * pure-function + injected-`humanAgo` pattern as `stream-status.ts`.
8
- *
9
- * Two modes:
10
- * - No `since` provided: classic roster — one line per drone with
11
- * label, role, and `last seen` relative time.
12
- * - `since` provided: each line additionally carries an `awake`
13
- * marker if the drone's `last_seen` is after the resolved
14
- * timestamp, otherwise a `stale-since-<relative>` marker derived
15
- * from the resolved timestamp the server echoed back.
16
- */
17
- /**
18
- * gh#503 — render-layer display labels for the wake-path wire enum.
19
- *
20
- * The gh#406 SLI MEASURES a streak of unanswered directed challenges; it
21
- * cannot confirm a genuinely broken wake path ("deaf") — a seat that is
22
- * alive but idle-and-text-only looks identical past the SLA. So the
23
- * roster shows the measured-confidence label, not the unconfirmed verdict:
24
- * degraded (1 miss) → `unverified` (signal seen, not yet corroborated)
25
- * deaf (2+ miss) → `unresponsive` (no response past the SLA)
26
- *
27
- * The wire enum VALUES (`live`/`degraded`/`deaf`), computed server-side by
28
- * `wakePathFromStreak`, are intentionally UNCHANGED — this is a display-only
29
- * relabel within gh#503's copy-only scope (renaming the enum would be a
30
- * wire + schema change). An operator who wants a confirmed read cross-checks
31
- * `borg:stream-status` on the affected seat. Unknown wire values fall back
32
- * to the raw value so a newer server can't break an older client's roster.
33
- */
34
- const WAKE_PATH_DISPLAY = {
35
- degraded: 'unverified',
36
- deaf: 'unresponsive',
37
- };
38
- export function formatRoleAgentLabel(roleName, agentKind) {
39
- return agentKind ? `${roleName}, ${agentKind}` : roleName;
40
- }
41
- export function renderRoster(inputs) {
42
- const { cubeName, drones, roles, resolvedSince, humanAgo } = inputs;
43
- const roleById = new Map();
44
- for (const r of roles)
45
- roleById.set(r.id, r);
46
- const lines = [];
47
- lines.push(`# Drones in cube: ${cubeName}`);
48
- lines.push('');
49
- if (resolvedSince) {
50
- // Surface the liveness-probe context so the reader knows what the
51
- // `awake`/`stale-since-X` column is measured against.
52
- lines.push(`_Liveness probe since ${resolvedSince} (${humanAgo(resolvedSince)}). \`awake\` = drone posted to the cube log after that point._`);
53
- lines.push('');
54
- }
55
- if (!drones.length) {
56
- lines.push('_(no drones connected)_');
57
- return lines.join('\n');
58
- }
59
- for (const d of drones) {
60
- const role = roleById.get(d.role_id);
61
- const roleName = role?.name ?? 'unknown';
62
- const roleLabel = formatRoleAgentLabel(roleName, d.agent_kind);
63
- const lastSeen = humanAgo(d.last_seen);
64
- const wakePathMarker = d.wake_path && d.wake_path !== 'live'
65
- ? ` · \`wake-path:${WAKE_PATH_DISPLAY[d.wake_path] ?? d.wake_path}\``
66
- : '';
67
- const wakePathClassMarker = d.wake_path_alert_class && d.wake_path_alert_class !== 'independent'
68
- ? ` · \`wake-path-class:${d.wake_path_alert_class}\``
69
- : '';
70
- const regenCountMarker = typeof d.regen_count === 'number' ? ` · \`regen-count:${d.regen_count}\`` : '';
71
- if (resolvedSince) {
72
- // T2.1 awake/stale column. `seen_since === true` → drone called a
73
- // tool after the resolved timestamp; treat as awake. False or
74
- // missing → stale. Missing should not happen when the server
75
- // echoed a since, but defending against a shape mismatch is
76
- // cheap and matches the renderer's no-server-assumptions
77
- // discipline established in stream-status.ts.
78
- //
79
- // Per drone-4's polish NIT on the original T2.1 ship: the marker
80
- // is just `stale` (not `stale-since-<relative>`) because the
81
- // header already anchors the reference point and per-row "since
82
- // when" would be identical across all stale rows in a single
83
- // probe call — pure redundancy. The per-row `last seen X ago`
84
- // field carries the diagnostic detail for "how stale is this
85
- // particular drone."
86
- const isAwake = d.seen_since === true;
87
- const marker = isAwake ? '`awake`' : '`stale`';
88
- lines.push(`- **${d.label}** (${roleLabel}) — last seen ${lastSeen} · ${marker}${regenCountMarker}${wakePathMarker}${wakePathClassMarker}`);
89
- }
90
- else {
91
- lines.push(`- **${d.label}** (${roleLabel}) — last seen ${lastSeen}${regenCountMarker}${wakePathMarker}${wakePathClassMarker}`);
92
- }
93
- }
94
- return lines.join('\n');
95
- }
96
- //# sourceMappingURL=roster-render.js.map
1
+ const d={degraded:"unverified",deaf:"unresponsive"};function w(t,s){return s?`${t}, ${s}`:t}function b(t){const{cubeName:s,drones:o,roles:h,resolvedSince:a,humanAgo:r}=t,c=new Map;for(const e of h)c.set(e.id,e);const n=[];if(n.push(`# Drones in cube: ${s}`),n.push(""),a&&(n.push(`_Liveness probe since ${a} (${r(a)}). \`awake\` = drone posted to the cube log after that point._`),n.push("")),!o.length)return n.push("_(no drones connected)_"),n.join(`
2
+ `);for(const e of o){const k=c.get(e.role_id)?.name??"unknown",l=w(k,e.agent_kind),p=r(e.last_seen),u=e.wake_path&&e.wake_path!=="live"?` \xB7 \`wake-path:${d[e.wake_path]??e.wake_path}\``:"",_=e.wake_path_alert_class&&e.wake_path_alert_class!=="independent"?` \xB7 \`wake-path-class:${e.wake_path_alert_class}\``:"",i=typeof e.regen_count=="number"?` \xB7 \`regen-count:${e.regen_count}\``:"";if(a){const $=e.seen_since===!0?"`awake`":"`stale`";n.push(`- **${e.label}** (${l}) \u2014 last seen ${p} \xB7 ${$}${i}${u}${_}`)}else n.push(`- **${e.label}** (${l}) \u2014 last seen ${p}${i}${u}${_}`)}return n.join(`
3
+ `)}export{w as formatRoleAgentLabel,b as renderRoster};