@nanocollective/roster 0.1.0-alpha.2 → 0.1.0-alpha.20

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 (96) hide show
  1. package/README.md +65 -84
  2. package/dist/cli.js +4733 -2490
  3. package/docs/README.md +19 -11
  4. package/docs/agents.md +328 -13
  5. package/docs/architecture.md +13 -5
  6. package/docs/charters/cmo.md +69 -0
  7. package/docs/charters/cto.md +71 -0
  8. package/docs/charters/support.md +60 -0
  9. package/docs/commands.md +90 -11
  10. package/docs/concepts.md +64 -12
  11. package/docs/cost.md +39 -3
  12. package/docs/developing.md +16 -21
  13. package/docs/doctor-codes.md +21 -6
  14. package/docs/export.md +2 -1
  15. package/docs/extending.md +13 -4
  16. package/docs/getting-started.md +118 -80
  17. package/docs/images/brain.jpg +0 -0
  18. package/docs/images/org.jpg +0 -0
  19. package/docs/images/prompt.jpg +0 -0
  20. package/docs/images/setup-org.jpg +0 -0
  21. package/docs/images/setup-plan.jpg +0 -0
  22. package/docs/images/staff.jpg +0 -0
  23. package/docs/manual-steps.md +94 -101
  24. package/docs/memory.md +29 -8
  25. package/docs/org-yaml.md +76 -11
  26. package/docs/portal.md +261 -47
  27. package/docs/prompts.md +77 -11
  28. package/docs/security.md +51 -7
  29. package/docs/session-workflow.md +51 -21
  30. package/docs/staff-yaml.md +16 -7
  31. package/docs/troubleshooting.md +23 -20
  32. package/docs/upgrading.md +6 -0
  33. package/docs/writing-a-charter.md +33 -17
  34. package/package.json +1 -1
  35. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
  36. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
  37. package/templates/brain/CHARTER.md +3 -3
  38. package/templates/brain/README.md +1 -0
  39. package/templates/brain/log/decisions.md +3 -0
  40. package/templates/brain/staff.yaml +0 -1
  41. package/templates/brain/strategy/ideas.md +7 -0
  42. package/templates/ops/.github/workflows/session.yaml +117 -40
  43. package/templates/ops/agents.mjs +127 -8
  44. package/templates/ops/compose.mjs +77 -7
  45. package/templates/ops/inflight.mjs +157 -0
  46. package/templates/ops/org/operating.md +21 -7
  47. package/templates/ops/org/voice.md +9 -0
  48. package/templates/ops/prompts/_identity.md +8 -1
  49. package/templates/ops/prompts/_inflight.md +14 -0
  50. package/templates/ops/prompts/_paths.md +2 -1
  51. package/templates/ops/prompts/daily.md +16 -7
  52. package/templates/ops/prompts/mention.md +18 -2
  53. package/templates/ops/run-record.mjs +144 -0
  54. package/templates/portal/css/base.css +238 -64
  55. package/templates/portal/css/brain.css +30 -20
  56. package/templates/portal/css/diff.css +15 -10
  57. package/templates/portal/css/graph.css +12 -7
  58. package/templates/portal/css/health.css +32 -11
  59. package/templates/portal/css/inbox.css +117 -14
  60. package/templates/portal/css/layout.css +93 -41
  61. package/templates/portal/css/markdown.css +57 -15
  62. package/templates/portal/css/runs.css +13 -0
  63. package/templates/portal/css/setup.css +83 -39
  64. package/templates/portal/index.html +24 -3
  65. package/templates/portal/js/api.js +65 -4
  66. package/templates/portal/js/app.js +112 -12
  67. package/templates/portal/js/dialog.js +94 -4
  68. package/templates/portal/js/dom.js +25 -0
  69. package/templates/portal/js/icons.js +8 -1
  70. package/templates/portal/js/lightbox.js +273 -0
  71. package/templates/portal/js/md.js +23 -6
  72. package/templates/portal/js/mdedit.js +84 -0
  73. package/templates/portal/js/mention.js +264 -0
  74. package/templates/portal/js/refresh.js +136 -6
  75. package/templates/portal/js/state.js +55 -8
  76. package/templates/portal/js/views/app.js +23 -5
  77. package/templates/portal/js/views/checklist.js +29 -10
  78. package/templates/portal/js/views/credential.js +98 -0
  79. package/templates/portal/js/views/docs.js +94 -4
  80. package/templates/portal/js/views/files.js +58 -14
  81. package/templates/portal/js/views/graph.js +1 -1
  82. package/templates/portal/js/views/health.js +178 -37
  83. package/templates/portal/js/views/inbox.js +938 -98
  84. package/templates/portal/js/views/memory.js +16 -1
  85. package/templates/portal/js/views/org.js +124 -104
  86. package/templates/portal/js/views/orgedit.js +213 -0
  87. package/templates/portal/js/views/paste.js +33 -7
  88. package/templates/portal/js/views/prompt.js +61 -67
  89. package/templates/portal/js/views/repos.js +20 -15
  90. package/templates/portal/js/views/runonce.js +94 -0
  91. package/templates/portal/js/views/runs.js +165 -0
  92. package/templates/portal/js/views/setup.js +311 -83
  93. package/templates/portal/js/views/staff.js +143 -22
  94. package/templates/portal/js/yaml.js +134 -0
  95. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  96. package/templates/ops/prompts/pr-mention.md +0 -57
@@ -1,11 +1,14 @@
1
1
  /* Is the scaffolding still there, has the agent been running, and what does lint say. */
2
2
 
3
3
  import { post } from "../api.js";
4
+ import { askYes } from "../dialog.js";
4
5
  import { ago, el, esc, kb } from "../dom.js";
5
6
  import { inline } from "../md.js";
6
7
  import { render } from "../router.js";
7
8
  import { S, staff } from "../state.js";
8
9
  import { checklist } from "./checklist.js";
10
+ import { copyAmendBrief } from "./prompt.js";
11
+ import { runOnce } from "./runonce.js";
9
12
 
10
13
  const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
11
14
 
@@ -27,20 +30,14 @@ export function viewHealth(m) {
27
30
  const rig = s.rig ?? {};
28
31
  m.append(el("h1", { textContent: s.name + " · health" }));
29
32
 
30
- /* `roster doctor` runs over the whole org, so it is drawn once at the top rather than
31
- pretended to be per-staff. The comment this replaced said doctor "does not exist yet". */
32
- const org = el("div", { className: "card", style: "margin-bottom:16px" });
33
- org.append(el("h3", { textContent: "The org, from roster doctor" }));
34
- const rows = el("div");
35
- org.append(rows);
36
- m.append(org);
37
- checklist(rows, {});
38
- const errs = s.problems.filter((p) => p.level === "error");
39
- const warns = s.problems.filter((p) => p.level === "warning");
40
- m.append(el("p", { className: "sub", innerHTML:
41
- "<span class='" + (errs.length ? "err" : "ok") + "'>" + errs.length + " errors</span> · " +
42
- "<span class='warn'>" + warns.length + " warnings</span> · memory checks are the same ones " +
43
- "<code>roster lint</code> runs; the rest is read off the checkout." }));
33
+ m.append(
34
+ el("p", {
35
+ className: "sub",
36
+ textContent:
37
+ "The rig, then three sets of checks: the org, the prompts they are sent, and what " +
38
+ "they have written down.",
39
+ }),
40
+ );
44
41
 
45
42
  /* The rig: is the scaffolding still there, and has the agent actually been running. This
46
43
  is everything answerable without the GitHub API — installation grants, secrets and
@@ -82,11 +79,86 @@ export function viewHealth(m) {
82
79
  }
83
80
  m.append(g);
84
81
 
85
- m.append(el("div", { className: "hsect", textContent: "Memory problems" }));
82
+ /* Three sets of checks, and they were three different-looking things: hairline rows for
83
+ doctor, bordered boxes for the prompt audit, cards with two buttons each for lint. Same
84
+ question in all three, so one shape: a heading that carries its own count, a line saying
85
+ what the section is, the findings as rows, and the section's actions at the end. */
86
+ /* A workflow that has never run is unproven, and the way to prove it is to run it. Here
87
+ because Health is where "unproven" is read; a success re-runs the checks below. */
88
+ let doctor = null;
89
+ m.append(
90
+ runOnce({
91
+ staff: s.handle,
92
+ name: s.name,
93
+ onDone: (ok) => ok && doctor && checklist(doctor.body, { shell: doctor }),
94
+ }),
95
+ );
86
96
 
87
- if (!s.problems.length) {
88
- m.append(el("p", { className: "empty", textContent: "Nothing to fix." }));
89
- return;
97
+ doctor = section(m, "The org, from roster doctor", "doctor");
98
+ checklist(doctor.body, { shell: doctor });
99
+
100
+ /* What is wrong with what this agent is sent. It used to sit in the tree on the Prompt
101
+ screen, beside the prompt itself — which answers "what is sent", not "is it any good".
102
+ Both questions are health questions, so both are here. */
103
+ const prompt = section(m, "Prompt problems", "prompts");
104
+ promptProblems(prompt, s);
105
+
106
+ memoryProblems(section(m, "Memory problems", "memory"), s);
107
+ }
108
+
109
+ /**
110
+ * One section: a heading with a count, a body, and a row for whatever acts on all of it.
111
+ *
112
+ * Returned rather than built in one go because two of the three fill themselves in later, and
113
+ * a heading that appears with its count already right is worth the small indirection.
114
+ */
115
+ function section(m, title, key) {
116
+ const head = el("div", { className: "hsect" });
117
+ head.append(el("span", { textContent: title }));
118
+ const count = el("i");
119
+ head.append(count);
120
+ const body = el("div", { className: "hbody " + key });
121
+ const actions = el("div", { className: "row hacts" });
122
+ m.append(head, body, actions);
123
+ return {
124
+ body,
125
+ actions,
126
+ /** `n` findings, or a word when a count is the wrong shape of answer. */
127
+ say: (text) => { count.textContent = text; },
128
+ };
129
+ }
130
+
131
+ /** The same checks `roster lint` runs, over what this staff member has written down. */
132
+ function memoryProblems({ body, actions, say }, s) {
133
+ say(s.problems.length ? String(s.problems.length) : "clean");
134
+ body.append(
135
+ el("p", {
136
+ className: "hnote",
137
+ textContent: s.problems.length
138
+ ? "The same checks roster lint runs, over memory/INDEX.md."
139
+ : "Nothing roster lint can fault in memory/INDEX.md.",
140
+ }),
141
+ );
142
+ if (!s.problems.length) return;
143
+
144
+ for (const p of s.problems) {
145
+ const { row, body: text } = finding(
146
+ p.level === "error" ? "error" : "warning",
147
+ { html: inline(p.message) },
148
+ [p.rule, p.line ? "INDEX.md:" + p.line : (p.file ?? "")],
149
+ );
150
+ const status = el("span", { className: "meta" });
151
+ const btn = el("button", { className: "ghbtn", textContent: "Ask " + s.handle.toUpperCase() + " to fix" });
152
+ btn.onclick = () => askToFix(s, [p], btn, status);
153
+ const acts = el("div", { className: "row facts" }, [btn]);
154
+ if (p.slug) {
155
+ const go = el("button", { className: "ghbtn", textContent: "Show the fact" });
156
+ go.onclick = () => { S.openFile = "fact:" + p.slug; S.fileQuery = ""; S.view = "brain"; render(); };
157
+ acts.append(go);
158
+ }
159
+ acts.append(status);
160
+ text.append(acts);
161
+ body.append(row);
90
162
  }
91
163
 
92
164
  /* The portal's one power over an agent is that it acts as the human, so "fix this" is an
@@ -95,27 +167,96 @@ export function viewHealth(m) {
95
167
  const all = el("button", { className: "ghbtn primary",
96
168
  textContent: "Ask " + s.handle.toUpperCase() + " to fix all " + s.problems.length });
97
169
  all.onclick = () => askToFix(s, s.problems, all, allStatus);
98
- m.append(el("div", { className: "row", style: "margin-bottom:12px" }, [all, allStatus]));
170
+ actions.append(all, allStatus);
171
+ }
99
172
 
100
- for (const p of s.problems) {
101
- const d = el("div", { className: "card" });
102
- d.innerHTML = '<div class="row"><span class="' + (p.level === "error" ? "err" : "warn") +
103
- '" style="font:11px var(--mono)">' + p.level + "</span>" +
104
- '<span class="pill">' + esc(p.rule) + "</span>" +
105
- (p.line ? '<span class="meta">INDEX.md:' + p.line + "</span>" : "") + "</div>" +
106
- '<p style="margin:8px 0 0">' + inline(p.message) + "</p>";
173
+ /**
174
+ * One finding, in the shape every section uses: a level marker, what is wrong, and where.
175
+ *
176
+ * The marker carries the severity so the words do not have to. A row that opens with the word
177
+ * "warning" spends its first line saying something a glyph already said.
178
+ */
179
+ function finding(level, title, tags) {
180
+ const row = el("div", { className: "check " + (level === "error" ? "fail" : "warn") });
181
+ row.append(el("span", { className: "checkmark", textContent: level === "error" ? "✗" : "!" }));
182
+ const body = el("div");
183
+ // Plain text where there is any, markup only where a message carries its own.
184
+ const head = el("b");
185
+ if (title.html) head.innerHTML = title.html;
186
+ else head.textContent = title.text;
187
+ body.append(head);
188
+ for (const t of tags.filter(Boolean)) {
189
+ body.append(el("span", { className: "meta", textContent: t }));
190
+ }
191
+ row.append(body);
192
+ // Everything else goes in the second column, under the text rather than under the marker.
193
+ return { row, body };
194
+ }
107
195
 
108
- const status = el("span", { className: "meta" });
109
- const btn = el("button", { className: "ghbtn", textContent: "Ask " + s.handle.toUpperCase() + " to fix" });
110
- btn.onclick = () => askToFix(s, [p], btn, status);
111
- const actions = el("div", { className: "row fix" }, [btn, status]);
112
- if (p.slug) {
113
- const go = el("button", { className: "ghbtn", textContent: "Show the fact" });
114
- go.onclick = () => { S.openFile = "fact:" + p.slug; S.fileQuery = ""; S.view = "brain"; render(); };
196
+ /**
197
+ * Everything the audit says about this staff member's prompts, over every kind of run.
198
+ *
199
+ * Fetched rather than read off the export: composing three prompts and diffing their layers is
200
+ * not something to do on every page paint of every screen, and this is the only one that asks.
201
+ */
202
+ async function promptProblems({ body, say }, s) {
203
+ say("…");
204
+ body.append(el("p", { className: "hnote", textContent: "Composing the prompts…" }));
205
+ let data;
206
+ try {
207
+ data = await (
208
+ await fetch("/api/promptaudit?staff=" + encodeURIComponent(s.handle), { cache: "no-store" })
209
+ ).json();
210
+ } catch (e) {
211
+ say("?");
212
+ body.replaceChildren(el("p", { className: "hnote err", textContent: e.message }));
213
+ return;
214
+ }
215
+
216
+ const problems = data.problems ?? [];
217
+ const errors = data.errors ?? [];
218
+ say(problems.length ? String(problems.length) : errors.length ? "?" : "clean");
219
+ body.replaceChildren(
220
+ el("p", {
221
+ className: "hnote",
222
+ textContent: problems.length
223
+ ? "What the audit can be sure about, over every kind of run this staff member has."
224
+ : "Nothing the audit can fault in what this staff member is sent.",
225
+ }),
226
+ );
227
+ for (const bad of errors) {
228
+ body.append(
229
+ el("div", { className: "notice", textContent:
230
+ "The " + bad.kind + " prompt does not compose: " + bad.error }),
231
+ );
232
+ }
233
+ if (!problems.length) return;
234
+
235
+ /* Worst first, and each one carrying the sentence that fixes it. Knowing there is a problem
236
+ is the hard part; writing the paragraph that asks for the change is not. */
237
+ const rank = { error: 0, warning: 1, note: 2 };
238
+ for (const p of [...problems].sort((a, b) => (rank[a.level] ?? 3) - (rank[b.level] ?? 3))) {
239
+ const { row, body: text } = finding(p.level, { text: p.title }, [(p.kinds ?? []).join(", ")]);
240
+ text.append(el("p", { textContent: p.detail }));
241
+
242
+ const note = el("span", { className: "meta" });
243
+ const fix = el("button", { className: "ghbtn", textContent: "Copy a prompt to fix this" });
244
+ fix.onclick = () => copyAmendBrief(s.handle, p.kind, p.want, fix, note);
245
+ const actions = el("div", { className: "row facts" }, [fix]);
246
+ if (p.path) {
247
+ const go = el("button", { className: "ghbtn", textContent: "Open the file" });
248
+ // The file is a layer of a prompt, so it opens where prompts are read.
249
+ go.onclick = () => {
250
+ S.promptKind = p.kind;
251
+ S.promptOpen = p.path;
252
+ S.view = "prompt";
253
+ render();
254
+ };
115
255
  actions.append(go);
116
256
  }
117
- d.append(actions);
118
- m.append(d);
257
+ actions.append(note);
258
+ text.append(actions);
259
+ body.append(row);
119
260
  }
120
261
  }
121
262
 
@@ -135,13 +276,13 @@ export async function askToFix(s, problems, btn, status) {
135
276
  "",
136
277
  "| rule | where | what |",
137
278
  "|---|---|---|",
138
- ...problems.map((p) => "| `" + p.rule + "` | " + (p.line ? "INDEX.md:" + p.line : "—") + " | " +
279
+ ...problems.map((p) => "| `" + p.rule + "` | " + (p.line ? "INDEX.md:" + p.line : (p.file ?? "—")) + " | " +
139
280
  String(p.message).replace(/\|/g, "\\|") + " |"),
140
281
  "",
141
282
  "Fix in your next session and close this.",
142
283
  ].join("\n");
143
284
 
144
- if (!confirm("Open an issue in " + s.brain + "?")) return;
285
+ if (!(await askYes({ title: "Open an issue in " + s.brain + "?", confirm: "Open it" }))) return;
145
286
  btn.disabled = true;
146
287
  status.textContent = "opening…";
147
288
  status.className = "meta";