projectkeeper 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +283 -2
  4. package/README.zh-CN.md +285 -0
  5. package/THIRD_PARTY_NOTICES.md +53 -0
  6. package/bin/pk.mjs +6 -0
  7. package/bin/postinstall.mjs +5 -0
  8. package/bin/strip-types.mjs +28 -0
  9. package/package.json +68 -4
  10. package/patches/README.md +111 -0
  11. package/patches/pi-ai-0.85.0.ts +122 -0
  12. package/patches/pi-ai-0.87.1.ts +122 -0
  13. package/scripts/apply-pi-streaming-patch.ts +86 -0
  14. package/skills/clerk/coverage/SKILL.md +35 -0
  15. package/skills/clerk/cross-check/SKILL.md +118 -0
  16. package/skills/clerk/deepen-dispatch/SKILL.md +69 -0
  17. package/skills/clerk/lane-follow-up/SKILL.md +94 -0
  18. package/skills/clerk/lane-plan/SKILL.md +107 -0
  19. package/skills/clerk/lane-slot/SKILL.md +113 -0
  20. package/skills/clerk/lane-topic/SKILL.md +116 -0
  21. package/skills/clerk/orientation/SKILL.md +160 -0
  22. package/skills/clerk/reconcile/SKILL.md +77 -0
  23. package/skills/clerk/skeleton-dispatch/SKILL.md +43 -0
  24. package/skills/clerk/spot-check/SKILL.md +52 -0
  25. package/skills/clerk/synthesis/SKILL.md +65 -0
  26. package/skills/table-follow-up/SKILL.md +42 -0
  27. package/src/cli.ts +319 -0
  28. package/src/codemap/README.md +13 -0
  29. package/src/codemap/engines.ts +102 -0
  30. package/src/codemap/facts.ts +504 -0
  31. package/src/context/agent-entry.ts +232 -0
  32. package/src/context/ask.ts +97 -0
  33. package/src/context/assemble.ts +1058 -0
  34. package/src/context/code-source.ts +103 -0
  35. package/src/context/k-briefs.ts +114 -0
  36. package/src/context/lineage.ts +100 -0
  37. package/src/context/object-brief.ts +300 -0
  38. package/src/context/owner-words.ts +191 -0
  39. package/src/context/pack-fixture.ts +342 -0
  40. package/src/context/project-rules.ts +160 -0
  41. package/src/intake/intake.ts +392 -0
  42. package/src/intake/material-rules.ts +166 -0
  43. package/src/intake/removal.ts +316 -0
  44. package/src/intake/skipped.ts +72 -0
  45. package/src/keeper/adjustment.ts +565 -0
  46. package/src/keeper/bounds/boundary.ts +509 -0
  47. package/src/keeper/bounds/command.ts +1665 -0
  48. package/src/keeper/bounds/paths.ts +164 -0
  49. package/src/keeper/bounds/reads.ts +718 -0
  50. package/src/keeper/bounds/shell-write-guard.ts +521 -0
  51. package/src/keeper/capabilities.ts +136 -0
  52. package/src/keeper/clerk-steps.ts +151 -0
  53. package/src/keeper/clerk-tools.ts +1424 -0
  54. package/src/keeper/conversation.ts +253 -0
  55. package/src/keeper/evidence.ts +662 -0
  56. package/src/keeper/fake-provider.ts +132 -0
  57. package/src/keeper/held.ts +15 -0
  58. package/src/keeper/history-tools.ts +163 -0
  59. package/src/keeper/jobs/answering.ts +53 -0
  60. package/src/keeper/key-check.ts +105 -0
  61. package/src/keeper/ledger-tools.ts +295 -0
  62. package/src/keeper/link-check.ts +185 -0
  63. package/src/keeper/merge.ts +260 -0
  64. package/src/keeper/note-origin.ts +56 -0
  65. package/src/keeper/numbers-check.ts +102 -0
  66. package/src/keeper/open-in-pi.ts +57 -0
  67. package/src/keeper/organize/absence.ts +266 -0
  68. package/src/keeper/organize/carried-on.ts +214 -0
  69. package/src/keeper/organize/clerk-coverage.ts +293 -0
  70. package/src/keeper/organize/clerk-prompts.ts +132 -0
  71. package/src/keeper/organize/clerk.ts +1395 -0
  72. package/src/keeper/organize/coverage-gate.ts +19 -0
  73. package/src/keeper/organize/coverage-tools.ts +765 -0
  74. package/src/keeper/organize/entries.ts +153 -0
  75. package/src/keeper/organize/entry-gate.ts +289 -0
  76. package/src/keeper/organize/fill-tools.ts +718 -0
  77. package/src/keeper/organize/follow-up.ts +280 -0
  78. package/src/keeper/organize/generation-check.ts +504 -0
  79. package/src/keeper/organize/graph.ts +230 -0
  80. package/src/keeper/organize/lane-tools.ts +289 -0
  81. package/src/keeper/organize/looked-tools.ts +54 -0
  82. package/src/keeper/organize/materials.ts +243 -0
  83. package/src/keeper/organize/owner-lines.ts +493 -0
  84. package/src/keeper/organize/place-tools.ts +216 -0
  85. package/src/keeper/organize/placement-inference.ts +1045 -0
  86. package/src/keeper/organize/placement-time.ts +320 -0
  87. package/src/keeper/organize/placing.ts +821 -0
  88. package/src/keeper/organize/positioning.ts +33 -0
  89. package/src/keeper/organize/readiness.ts +47 -0
  90. package/src/keeper/organize/reading.ts +401 -0
  91. package/src/keeper/organize/round-blocks.ts +65 -0
  92. package/src/keeper/organize/round-news.ts +164 -0
  93. package/src/keeper/organize/round-open.ts +586 -0
  94. package/src/keeper/organize/schedule-state.ts +39 -0
  95. package/src/keeper/organize/schedule.ts +137 -0
  96. package/src/keeper/organize/service.ts +409 -0
  97. package/src/keeper/organize/skills.ts +116 -0
  98. package/src/keeper/organize/slots.ts +85 -0
  99. package/src/keeper/organize/stage-gate.ts +175 -0
  100. package/src/keeper/organize/stage-tools.ts +471 -0
  101. package/src/keeper/organize/standing-notes.ts +141 -0
  102. package/src/keeper/organize/table-follow-up.ts +330 -0
  103. package/src/keeper/organize/takeover-clerk.ts +832 -0
  104. package/src/keeper/organize/takeover-state.ts +103 -0
  105. package/src/keeper/organize/takeover.ts +216 -0
  106. package/src/keeper/organize/update-pending.ts +535 -0
  107. package/src/keeper/organize/workbench-placement.ts +92 -0
  108. package/src/keeper/own-keys.ts +114 -0
  109. package/src/keeper/owner-text.ts +165 -0
  110. package/src/keeper/project-folder.ts +334 -0
  111. package/src/keeper/prompts.ts +101 -0
  112. package/src/keeper/references.ts +177 -0
  113. package/src/keeper/relook/compose.ts +120 -0
  114. package/src/keeper/relook/prompts.ts +36 -0
  115. package/src/keeper/roles.ts +259 -0
  116. package/src/keeper/round-tools.ts +227 -0
  117. package/src/keeper/route.ts +148 -0
  118. package/src/keeper/rules.ts +97 -0
  119. package/src/keeper/runtime.ts +1858 -0
  120. package/src/keeper/scope-tools.ts +194 -0
  121. package/src/keeper/sendback-text.ts +83 -0
  122. package/src/keeper/step-timing.ts +207 -0
  123. package/src/keeper/testing/clerk-script.ts +212 -0
  124. package/src/keeper/testing/streaming-arguments.ts +57 -0
  125. package/src/keeper/tools.ts +2340 -0
  126. package/src/keeper/utterance-tools.ts +64 -0
  127. package/src/keeper/verbatim.ts +397 -0
  128. package/src/ledger/adapters.ts +186 -0
  129. package/src/ledger/arrangements.ts +257 -0
  130. package/src/ledger/blame.ts +58 -0
  131. package/src/ledger/code-engine-child.ts +69 -0
  132. package/src/ledger/code-engine.ts +548 -0
  133. package/src/ledger/code.ts +607 -0
  134. package/src/ledger/docs.ts +294 -0
  135. package/src/ledger/git-read.ts +321 -0
  136. package/src/ledger/index.ts +1850 -0
  137. package/src/ledger/item-range.ts +16 -0
  138. package/src/ledger/lines.ts +328 -0
  139. package/src/ledger/numbering.ts +269 -0
  140. package/src/ledger/ranges.ts +74 -0
  141. package/src/ledger/rebuild-worker.ts +15 -0
  142. package/src/ledger/rebuild.ts +387 -0
  143. package/src/ledger/repo-scan.ts +181 -0
  144. package/src/ledger/schema.ts +450 -0
  145. package/src/ledger/sessions.ts +134 -0
  146. package/src/ledger/text-scan.ts +466 -0
  147. package/src/ledger/time.ts +185 -0
  148. package/src/ledger/ts-symbols.ts +180 -0
  149. package/src/ledger/views.ts +377 -0
  150. package/src/mcp/server.ts +127 -0
  151. package/src/mcp/tools.ts +187 -0
  152. package/src/model/ids.ts +23 -0
  153. package/src/model/k-types.ts +912 -0
  154. package/src/model/time.ts +85 -0
  155. package/src/model/types.ts +1382 -0
  156. package/src/model/views-k.ts +707 -0
  157. package/src/model/vocab.ts +269 -0
  158. package/src/process/analysis.ts +79 -0
  159. package/src/process/breakpoint-candidates.ts +383 -0
  160. package/src/process/breakpoints.ts +746 -0
  161. package/src/process/claim-check.ts +295 -0
  162. package/src/process/delivery-links.ts +52 -0
  163. package/src/process/facts.ts +487 -0
  164. package/src/process/generations.ts +138 -0
  165. package/src/process/index.ts +243 -0
  166. package/src/process/integration.ts +128 -0
  167. package/src/process/placement.ts +107 -0
  168. package/src/process/plan.ts +276 -0
  169. package/src/process/sendbacks.ts +244 -0
  170. package/src/process/text.ts +185 -0
  171. package/src/process/units.ts +543 -0
  172. package/src/process/unnumbered.ts +431 -0
  173. package/src/process/work.ts +884 -0
  174. package/src/scope/candidates.ts +86 -0
  175. package/src/scope/decisions.ts +229 -0
  176. package/src/scope/discover.ts +741 -0
  177. package/src/scope/ignore.ts +126 -0
  178. package/src/scope/ignored-place.ts +93 -0
  179. package/src/scope/skip.ts +161 -0
  180. package/src/scope/toolchain.ts +180 -0
  181. package/src/scope/worktree.ts +187 -0
  182. package/src/server/api.ts +549 -0
  183. package/src/server/app.ts +595 -0
  184. package/src/server/drafts-api.ts +170 -0
  185. package/src/server/folders-api.ts +243 -0
  186. package/src/server/graph-view.ts +639 -0
  187. package/src/server/http.ts +224 -0
  188. package/src/server/k-agent-api.ts +56 -0
  189. package/src/server/k-rounds-d99.ts +91 -0
  190. package/src/server/k-views.ts +405 -0
  191. package/src/server/keeper-page.ts +446 -0
  192. package/src/server/keys-api.ts +239 -0
  193. package/src/server/project-folder-api.ts +104 -0
  194. package/src/server/vendor.ts +14 -0
  195. package/src/server/workbench-content.ts +561 -0
  196. package/src/sources/anchor.ts +129 -0
  197. package/src/sources/files.ts +267 -0
  198. package/src/sources/gitobs.ts +41 -0
  199. package/src/sources/history.ts +231 -0
  200. package/src/sources/scan-worker.ts +10 -0
  201. package/src/sources/sessions/locate.ts +292 -0
  202. package/src/sources/sessions/read.ts +346 -0
  203. package/src/sources/sessions/scope.ts +42 -0
  204. package/src/sources/sessions/utterances.ts +223 -0
  205. package/src/sources/watch.ts +215 -0
  206. package/src/store/json-file.ts +50 -0
  207. package/src/store/paths.ts +39 -0
  208. package/src/store/project-store.ts +295 -0
  209. package/src/store/versions.ts +172 -0
  210. package/src/store/workspace.ts +216 -0
  211. package/src/table/check.ts +333 -0
  212. package/src/table/follow-up-tools.ts +30 -0
  213. package/src/table/follow-up.ts +344 -0
  214. package/src/table/import.ts +610 -0
  215. package/src/table/project-home.ts +61 -0
  216. package/src/table/sessions.ts +168 -0
  217. package/src/table/takeover.ts +483 -0
  218. package/src/util/git-check.ts +29 -0
  219. package/src/util/git.ts +411 -0
  220. package/src/util/own-ts.ts +15 -0
  221. package/src/util/paths.ts +225 -0
  222. package/template/FILLING-IN.md +148 -0
  223. package/template/examples/papertrail.json +747 -0
  224. package/template/projectkeeper.schema.json +1001 -0
  225. package/ui/app.js +1135 -0
  226. package/ui/failures.js +82 -0
  227. package/ui/flyout.js +120 -0
  228. package/ui/folder-chooser.js +264 -0
  229. package/ui/glossary.js +156 -0
  230. package/ui/graph-fit.js +157 -0
  231. package/ui/graph-material.js +61 -0
  232. package/ui/graph-palette.js +109 -0
  233. package/ui/graph-tools.js +62 -0
  234. package/ui/graph.js +1712 -0
  235. package/ui/index.html +31 -0
  236. package/ui/k/code-view.js +230 -0
  237. package/ui/k/draft-view.js +93 -0
  238. package/ui/k/k-views.css +224 -0
  239. package/ui/k/round-tree.js +450 -0
  240. package/ui/k/scope-k.js +159 -0
  241. package/ui/k-data.js +118 -0
  242. package/ui/k-fold.js +106 -0
  243. package/ui/k-process.css +302 -0
  244. package/ui/k-process.js +934 -0
  245. package/ui/keeper-activity.js +175 -0
  246. package/ui/keeper-view.js +801 -0
  247. package/ui/keeper.css +101 -0
  248. package/ui/key-order.js +62 -0
  249. package/ui/list-fold.js +67 -0
  250. package/ui/markdown.d.ts +21 -0
  251. package/ui/markdown.js +472 -0
  252. package/ui/placement.d.ts +92 -0
  253. package/ui/placement.js +445 -0
  254. package/ui/popover-place.d.ts +16 -0
  255. package/ui/popover-place.js +89 -0
  256. package/ui/popover.js +198 -0
  257. package/ui/project-folder.js +37 -0
  258. package/ui/shell-state.js +40 -0
  259. package/ui/strip-rows.js +171 -0
  260. package/ui/styles.css +492 -0
  261. package/ui/theme-pick.js +36 -0
  262. package/ui/theme.js +74 -0
  263. package/ui/themes/LICENSE.md +13 -0
  264. package/ui/themes/README.md +44 -0
  265. package/ui/themes/TOKENS.md +291 -0
  266. package/ui/themes/a1/board.css +1557 -0
  267. package/ui/themes/a1/pk.css +392 -0
  268. package/ui/themes/b1/board.css +1163 -0
  269. package/ui/themes/b1/pk.css +218 -0
  270. package/ui/themes/d2/board.css +518 -0
  271. package/ui/themes/d2/pk.css +243 -0
  272. package/ui/themes/guard.css +73 -0
  273. package/ui/themes/i1/board.css +776 -0
  274. package/ui/themes/i1/pk.css +223 -0
  275. package/ui/themes/manifest.json +13 -0
  276. package/ui/views.js +2796 -0
@@ -0,0 +1,175 @@
1
+ // Keeper activity (Spec §6.9; D35, D105; CKC-03 AC-5, AC-12): the one list of what the Keeper is doing and has done, in a
2
+ // dialog. It opens from the Keeper's status line at the foot of the rail and from both pages of the Keeper view; the
3
+ // Keeper view keeps no second copy.
4
+ //
5
+ // A round is one row — which kind, when, its status, its time and cost — and its tree opens on a click: the ledger, the
6
+ // main agent with the stage it reached, the lanes it sent, the synthesis, the spot check, each with its scope, status,
7
+ // time and usage (k/round-tree.js). The briefs, reports and adoption records open on a further click there. Work that
8
+ // belongs to no round — an answer to the owner, a re-look, a request — is listed under the rounds.
9
+ import { api, append, clear, fmtRel, fmtTime, h, navigate, openDialog, refreshActivity, state, toast } from './app.js';
10
+ import { renderRoundTree } from './k/round-tree.js';
11
+ import { openDraft } from './k/draft-view.js';
12
+ import * as kData from './k-data.js';
13
+ import * as kProcess from './k-process.js';
14
+
15
+ const P = () => encodeURIComponent(state.projectId);
16
+ const fmtWall = (ms) => { if (ms == null) return '—'; const m = Math.round(ms / 60000); return m < 1 ? 'under a minute' : m < 90 ? `${m} min` : `${(m / 60).toFixed(1)} h`; };
17
+ const fmtCost = (c) => (c == null ? 'cost not reported' : `$${c.toFixed(2)}`);
18
+ const ROUND_NAME = { 'First usable': 'Takeover: first usable picture', Deepen: 'Takeover: deepening', 'Follow up': 'Follow up' };
19
+ const STARTED_BY = { owner: 'you pressed Follow up', schedule: 'started by the schedule', continuous: 'started by Continuous' };
20
+
21
+ /** The rounds the owner opened stay open while the list is redrawn for new data. */
22
+ const openRounds = new Set();
23
+
24
+ /** The round that recorded the project's rules, or the plan and focus, opens them in Project scope (Spec §6.9, §3.7). */
25
+ function opensLinks(j) {
26
+ if (!j.opens?.rules && !j.opens?.plan) return null;
27
+ const go = (rest) => { const d = document.querySelector('#dialog'); if (d?.open) d.close(); navigate(state.projectId, 'scope', rest); };
28
+ return h('div', { class: 'row wrap' },
29
+ j.opens.rules ? h('button', { class: 'text-btn', title: 'Open How this project works in Project scope', onClick: () => go('rules') }, `Project rules (${j.opens.rules})`) : null,
30
+ j.opens.plan ? h('button', { class: 'text-btn', title: 'Open the organizing plan and focus in Project scope', onClick: () => go('plan') }, 'Organizing plan and focus') : null);
31
+ }
32
+
33
+ /**
34
+ * The row drawn for each round and what it was drawn from. A round whose data has not changed keeps its element when the
35
+ * list is redrawn, so a tree the owner is reading — a brief open, an account unfolded — is not rebuilt because another
36
+ * round moved on.
37
+ */
38
+ const drawn = new Map();
39
+ const sameAs = (round, failures) => JSON.stringify([{ ...round, wallMs: round.endedAt ? round.wallMs : null }, failures]);
40
+
41
+ /** One round: a row, and under it — once opened — its tree. */
42
+ function roundRow(round, ctx, render) {
43
+ const failures = (ctx.failures ?? []).filter((f) => round.steps.some((s) => s.jobId === f.ref));
44
+ const key = sameAs(round, failures);
45
+ const kept = drawn.get(round.id);
46
+ if (kept && kept.key === key && kept.projectId === state.projectId) return kept.el;
47
+ const el = buildRoundRow(round, ctx, render, failures);
48
+ drawn.set(round.id, { key, el, projectId: state.projectId });
49
+ return el;
50
+ }
51
+ function buildRoundRow(round, ctx, render, failures) {
52
+ const body = h('div', { class: 'ka-round-tree' });
53
+ const draw = () => { if (!body.firstChild) renderRoundTree(body, [round], ctx); };
54
+ const running = round.status === 'Running';
55
+ const stop = async (e) => {
56
+ e.preventDefault(); e.stopPropagation();
57
+ try {
58
+ for (const id of [round.rootJobId, ...round.steps.filter((s) => /running|queued|paused|waiting/i.test(s.status)).map((s) => s.jobId)].filter(Boolean)) await api(`/api/projects/${P()}/keeper/jobs/${encodeURIComponent(id)}/stop`, { method: 'POST' });
59
+ toast('Stopping the round: what is saved stays, and it can be continued');
60
+ } catch (err) { toast(err.message); }
61
+ await render();
62
+ };
63
+ const result = round.kind === 'Follow up' ? ctx.resultOf?.(round) ?? null : null;
64
+ const el = h('details', { class: 'ka-round', 'data-round': round.id, 'data-kind': round.kind, open: openRounds.has(round.id),
65
+ onToggle: (e) => { if (e.target.open) { openRounds.add(round.id); draw(); } else openRounds.delete(round.id); } },
66
+ h('summary', { class: 'ka-round-row' },
67
+ h('span', { class: 'tag blue' }, ROUND_NAME[round.kind] ?? round.kind),
68
+ h('b', {}, `Round ${round.number}`),
69
+ round.kind === 'Deepen' && round.depth ? h('span', { class: 'muted' }, round.depth) : null,
70
+ round.kind === 'Follow up' && round.startedBy ? h('span', { class: 'muted' }, STARTED_BY[round.startedBy] ?? round.startedBy) : null,
71
+ h('span', { class: 'muted' }, fmtTime(round.startedAt)),
72
+ h('span', { class: `tag ${failures.length ? 'red' : /done/i.test(round.status) ? 'green' : running ? 'amber' : ''}` }, failures.length ? `${round.status} · ${failures.length} not finished` : round.status),
73
+ h('span', { class: 'muted' }, `${fmtWall(round.wallMs)} · ${fmtCost(round.usage?.cost ?? null)}`),
74
+ result ? h('span', { class: 'faint ka-round-result' }, result.line) : null,
75
+ running ? h('button', { class: 'btn small', title: 'Stop this round: the whole tree stops; what is saved stays', onClick: stop }, 'Stop') : null),
76
+ body);
77
+ if (openRounds.has(round.id)) draw();
78
+ return el;
79
+ }
80
+
81
+ export async function renderActivity(body) {
82
+ const render = () => renderActivity(body);
83
+ const [a, rounds, summary, ov] = await Promise.all([
84
+ api(`/api/projects/${P()}/activity`),
85
+ kData.getRounds(state.projectId).catch(() => null),
86
+ api(`/api/projects/${P()}`).catch(() => null),
87
+ api(`/api/projects/${P()}/overview?since=&selection=`).catch(() => null),
88
+ ]);
89
+ const scroll = body.closest('.dialog-body')?.scrollTop ?? 0;
90
+ clear(body);
91
+ const pauseBtn = h('button', { class: 'btn small', onClick: async () => { await api(`/api/projects/${P()}/keeper/${a.organizingPaused ? 'resume' : 'pause'}`, { method: 'POST' }); await render(); await refreshActivity(); } }, a.organizingPaused ? 'Resume organizing' : 'Pause organizing');
92
+ const relookBtn = h('button', { class: 'btn small', disabled: a.status === 'Not connected', title: 'Ask the Keeper to re-look at the whole project now', onClick: async () => { await api(`/api/projects/${P()}/relook`, { method: 'POST', body: { kind: 'project' } }); toast('Product re-look requested'); await render(); } }, 'Request product re-look');
93
+ const close = () => { const d = document.querySelector('#dialog'); if (d?.open) d.close(); };
94
+ append(body, h('div', { class: 'row spread' },
95
+ h('div', {}, h('b', {}, a.status), a.reason ? h('div', { class: 'muted' }, a.reason) : null, h('div', { class: 'muted' }, 'Agent: ', a.agent ?? '—', ' · Model: ', a.model ? `${a.model.provider}/${a.model.id}${a.model.thinking ? ':' + a.model.thinking : ''}` : '—')),
96
+ h('div', { class: 'row' }, h('button', { class: 'text-btn', title: 'The schedule of daily organizing is set on the Daily page', onClick: () => { close(); navigate(state.projectId, 'keeper', 'daily'); } }, 'Schedule'), relookBtn, pauseBtn)));
97
+
98
+ // ── the rounds: one row each, the tree on a click ──
99
+ const results = new Map((ov?.roundResults ?? []).filter((x) => x.clerkRoundId).map((x) => [x.clerkRoundId, x]));
100
+ const ctx = {
101
+ fetchDoc: (roundId, docId) => kData.getRoundDoc(state.projectId, roundId, docId), openJob: () => undefined,
102
+ // A session draft the session-drafts step wrote opens where the round is drilled into (CKC-23 AC-18).
103
+ openDraft: (draftId) => openDraft(draftId, kProcess.draftCtx()),
104
+ // A deepening read by reading assignments: what each sweep read of its plan, from the coverage (§3.7).
105
+ deepening: summary?.coverage?.takeover?.deepening ?? null,
106
+ // A Follow up round's one result (§5.5), and the way to open it (§6.9).
107
+ resultOf: (round) => results.get(round.id) ?? null,
108
+ openResult: async (result) => { const { openRound } = await import('./views.js'); openRound(result.id, { name: result.name }); },
109
+ // What of a round did not finish, with why (§3.10): the coverage's failure list, and Retry or Continue on each.
110
+ failures: summary?.coverage?.scopes?.find((s) => s.id === 'project')?.failed ?? [],
111
+ runAgain: async (jobId, action) => {
112
+ try {
113
+ const r = await api(`/api/projects/${P()}/keeper/jobs/${encodeURIComponent(jobId)}/${action}`, { method: 'POST' });
114
+ toast(r.ok ? (action === 'retry' ? 'Running it again' : 'Continuing it') : 'It cannot run again now');
115
+ } catch (e) { toast(e.message); }
116
+ await render();
117
+ },
118
+ };
119
+ append(body, h('h4', { class: 'ka-head' }, `Rounds (${rounds?.length ?? 0})`));
120
+ if (!rounds || !rounds.length) append(body, h('div', { class: 'faint', id: 'ka-no-rounds' }, rounds ? 'No round has run yet. A takeover starts on the Takeover page of the Keeper view.' : kData.noData()));
121
+ else append(body, h('div', { class: 'ka-rounds', id: 'ka-rounds' }, ...rounds.map((r) => roundRow(r, ctx, render))));
122
+
123
+ // ── work outside the rounds: answers, re-looks, requests; a job and what it delegated are one piece of work ──
124
+ const inRound = new Set((rounds ?? []).flatMap((r) => [r.rootJobId, ...r.steps.flatMap((s) => [s.jobId, ...s.children.map((c) => c.jobId)])]).filter(Boolean));
125
+ const byId = new Map(a.jobs.map((j) => [j.id, j]));
126
+ const partOfRound = (j) => { for (let x = j; x; x = x.parentJobId ? byId.get(x.parentJobId) : null) if (inRound.has(x.id) || x.scope?.kind === 'clerk-round' || x.scope?.kind === 'clerk-step') return true; return false; };
127
+ const others = a.jobs.filter((j) => !partOfRound(j));
128
+ const otherIds = new Set(others.map((j) => j.id));
129
+ const kids = new Map();
130
+ for (const j of others) if (j.parentJobId && otherIds.has(j.parentJobId)) kids.set(j.parentJobId, [...(kids.get(j.parentJobId) || []), j]);
131
+ const roots = others.filter((j) => !j.parentJobId || !otherIds.has(j.parentJobId)).sort((x, y) => (y.queuedAt || '').localeCompare(x.queuedAt || ''));
132
+ const jobs = [];
133
+ const walk = (j, depth) => { jobs.push({ ...j, depth }); for (const c of (kids.get(j.id) || []).sort((x, y) => (x.queuedAt || '').localeCompare(y.queuedAt || ''))) walk(c, depth + 1); };
134
+ for (const r of roots.slice(0, 60)) walk(r, 0);
135
+ append(body, h('h4', { class: 'ka-head' }, `Other work (${jobs.length})`));
136
+ if (jobs.length === 0) append(body, h('div', { class: 'faint' }, 'No other Keeper work: answers to you, re-looks and requests are listed here.'));
137
+ else {
138
+ append(body, h('table', { class: 'list ka-jobs' }, h('thead', {}, h('tr', {}, h('th', {}, 'Kind'), h('th', {}, 'Initiator'), h('th', {}, 'Scope'), h('th', {}, 'Status'), h('th', {}, 'Saved results'), h('th', {}, 'Started'), h('th', {}, 'Usage'), h('th', {}))),
139
+ h('tbody', {}, ...jobs.map((j) => h('tr', {},
140
+ h('td', { style: j.depth ? { paddingLeft: `${8 + j.depth * 16}px` } : null }, j.depth ? h('span', { class: 'faint' }, '↳ ') : null, j.kind, j.requestBasis ? h('div', {}, h('span', { class: 'tag amber' }, j.requestBasis.label)) : null),
141
+ h('td', {}, j.initiator),
142
+ h('td', {}, j.scope.label),
143
+ h('td', {}, h('span', { class: `tag ${j.status === 'Running' ? 'amber' : j.status === 'Done' ? 'green' : j.status === 'Failed' ? 'red' : ''}` }, j.status), j.error ? h('div', { class: 'faint' }, j.error.slice(0, 160)) : null,
144
+ j.boundaryDenials > 0 ? h('div', { class: 'faint', title: 'Reads or shell commands the read boundary refused during this job' }, `${j.boundaryDenials} read${j.boundaryDenials === 1 ? '' : 's'} outside the boundary denied${(j.boundaryDenialsWithDelegated ?? j.boundaryDenials) > j.boundaryDenials ? ` · ${j.boundaryDenialsWithDelegated} with subagents` : ''}`) : null),
145
+ h('td', {}, j.savedResults.length ? j.savedResults.slice(0, 5).map((r) => h('div', { class: 'faint' }, r.label)) : '—', opensLinks(j)),
146
+ h('td', {}, j.startedAt ? fmtTime(j.startedAt) : h('span', { class: 'faint' }, `queued ${fmtRel(j.queuedAt)}`)),
147
+ h('td', {}, `${j.usage.input}↑ ${j.usage.output}↓${j.usage.cost == null ? '' : ' $' + j.usage.cost.toFixed(3)}`,
148
+ j.delegated ? h('div', { class: 'faint', title: 'This job and everything it delegated to' }, `with ${j.delegated} subagent${j.delegated === 1 ? '' : 's'}: ${j.usageWithDelegated.input}↑ ${j.usageWithDelegated.output}↓`) : null),
149
+ h('td', {}, ['Running', 'Queued', 'Paused', 'Waiting for quota'].includes(j.status) ? h('button', { class: 'btn small', onClick: async () => { await api(`/api/projects/${P()}/keeper/jobs/${j.id}/stop`, { method: 'POST' }); await render(); } }, 'Stop') : j.status === 'Stopped' && j.agent !== 'program' ? h('button', { class: 'btn small', onClick: async () => { await api(`/api/projects/${P()}/keeper/jobs/${j.id}/continue`, { method: 'POST' }); await render(); } }, 'Continue') : j.status === 'Failed' && j.agent !== 'program' ? h('button', { class: 'btn small', onClick: async () => { await api(`/api/projects/${P()}/keeper/jobs/${j.id}/retry`, { method: 'POST' }); await render(); } }, 'Retry') : null,
150
+ j.steps?.length ? h('details', { class: 'fold' }, h('summary', {}, `${j.steps.length} steps`), h('div', { class: 'content' }, ...j.steps.slice(-40).map((s) => h('div', { class: `mono ${s.isError ? 'red' : ''}` }, `${s.tool} ${s.target}${s.summary ? ' — ' + s.summary : ''}`)))) : null,
151
+ j.sessionFile ? h('button', { class: 'text-btn', onClick: async () => { const r = await api(`/api/projects/${P()}/keeper/open-in-pi`, { method: 'POST', body: { jobId: j.id } }); toast(r.message); } }, 'Open in pi') : null))))));
152
+ }
153
+ const scroller = body.closest('.dialog-body');
154
+ if (scroller) scroller.scrollTop = scroll;
155
+ }
156
+
157
+ /** Open Keeper activity; with `roundId`, that round's tree is open and in view. */
158
+ export async function openKeeperActivity({ roundId = null } = {}) {
159
+ if (roundId) openRounds.add(roundId);
160
+ const body = h('div', { class: 'stack ka-body', id: 'keeper-activity' });
161
+ if (!document.getElementById('keeper-css')) document.head.append(h('link', { id: 'keeper-css', rel: 'stylesheet', href: '/keeper.css' }));
162
+ const dialog = openDialog('Keeper activity', [body]);
163
+ dialog.classList.add('wide');
164
+ const render = () => renderActivity(body);
165
+ await render();
166
+ dialog._rerender = render;
167
+ const row = roundId ? body.querySelector(`[data-round="${CSS.escape(roundId)}"]`) : null;
168
+ if (row) { row.open = true; row.scrollIntoView({ block: 'start' }); }
169
+ }
170
+
171
+ /** The view the shell opens from the rail's Keeper line, and tells of the Keeper's events. */
172
+ export const activityView = {
173
+ open: () => openKeeperActivity(),
174
+ onEvent() { const d = document.querySelector('#dialog'); if (d?.open && d._rerender) d._rerender(); },
175
+ };