@azure-id/orc 2.0.2 → 2.1.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 (121) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README-id.md +52 -23
  3. package/README.md +50 -22
  4. package/bin/build-agents.js +24 -25
  5. package/bin/clear-logs.js +620 -0
  6. package/bin/cli.js +1155 -158
  7. package/bin/fix.js +459 -0
  8. package/bin/gotcha.js +288 -8
  9. package/bin/habit.js +1538 -1453
  10. package/bin/mockrun-catalog.js +3 -0
  11. package/bin/onboarding-content.js +2 -1
  12. package/bin/pricing.json +28 -21
  13. package/bin/trace-write.js +869 -657
  14. package/bin/verify-contracts.js +108 -27
  15. package/bin/verify-package.js +17 -6
  16. package/bin/webui/api.js +125 -6
  17. package/bin/webui/css/panels/hookui.css +79 -0
  18. package/bin/webui/fixtures/behaviour.js +30 -0
  19. package/bin/webui/fixtures/extra.js +9 -9
  20. package/bin/webui/fixtures/flow.js +81 -81
  21. package/bin/webui/fixtures/hookui.js +506 -431
  22. package/bin/webui/fixtures/index.js +32 -3
  23. package/bin/webui/fixtures/knowledge.js +2 -2
  24. package/bin/webui/fixtures/maintenance.js +123 -42
  25. package/bin/webui/fixtures/rules.js +1007 -1002
  26. package/bin/webui/fixtures/settings.js +6 -7
  27. package/bin/webui/fixtures/stats.js +9 -6
  28. package/bin/webui/fixtures/wait.js +98 -97
  29. package/bin/webui/i18n/en/behaviour.json +13 -0
  30. package/bin/webui/i18n/en/hookui.json +290 -194
  31. package/bin/webui/i18n/en/maintenance.json +71 -52
  32. package/bin/webui/i18n/en/settings.json +60 -60
  33. package/bin/webui/i18n/id/behaviour.json +13 -0
  34. package/bin/webui/i18n/id/hookui.json +290 -194
  35. package/bin/webui/i18n/id/maintenance.json +71 -52
  36. package/bin/webui/i18n/id/settings.json +60 -60
  37. package/bin/webui/js/05-banners.js +181 -172
  38. package/bin/webui/js/06-edit.js +193 -183
  39. package/bin/webui/js/panels/behaviour.js +77 -12
  40. package/bin/webui/js/panels/hookui.js +2196 -1691
  41. package/bin/webui/js/panels/maintenance.js +336 -235
  42. package/bin/webui/js/panels/settings.js +726 -719
  43. package/mock-run/INDEX.md +1 -0
  44. package/mock-run/extra-slots.md +1 -1
  45. package/mock-run/habits.md +1 -1
  46. package/mock-run/orc-aftermath.md +392 -392
  47. package/mock-run/orc-budget.md +29 -33
  48. package/mock-run/orc-cli.md +200 -200
  49. package/mock-run/orc-explain.md +86 -86
  50. package/mock-run/orc-extra.md +389 -392
  51. package/mock-run/orc-fix.md +87 -0
  52. package/mock-run/orc-pattern.md +112 -112
  53. package/mock-run/orc.md +157 -157
  54. package/package.json +1 -1
  55. package/templates/agents/MODEL-MAPPING.md +32 -29
  56. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
  57. package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
  58. package/templates/agents/orc-executor-haiku-4-5.md +1 -1
  59. package/templates/agents/orc-executor-opus-5-low.md +1 -1
  60. package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
  61. package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
  62. package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
  63. package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
  64. package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
  65. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
  66. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
  67. package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
  68. package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
  69. package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
  70. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
  71. package/templates/commands/orc-fix.md +18 -0
  72. package/templates/commands/orc-wiki.md +64 -64
  73. package/templates/hooks/README.md +75 -7
  74. package/templates/hooks/orc-session-hook.js +78 -3
  75. package/templates/hooks/orc-statusline-render.js +317 -23
  76. package/templates/hooks/orc-statusline.js +162 -49
  77. package/templates/hooks/orc-subagent-line.js +23 -5
  78. package/templates/hooks/orc-weather-fetch.js +123 -0
  79. package/templates/skills/_shared/extra-dispatch.md +5 -5
  80. package/templates/skills/_shared/gotchas.md +27 -7
  81. package/templates/skills/_shared/habits.md +2 -2
  82. package/templates/skills/_shared/opus5-only.md +14 -14
  83. package/templates/skills/_shared/phases/review.md +2 -2
  84. package/templates/skills/_shared/phases/trace-verbs.md +17 -3
  85. package/templates/skills/_shared/phases/trace.md +136 -136
  86. package/templates/skills/_shared/review-slice.md +28 -1
  87. package/templates/skills/_shared/wait.md +1 -0
  88. package/templates/skills/orc/README.md +2 -2
  89. package/templates/skills/orc/SKILL.md +1 -1
  90. package/templates/skills/orc/config.md +25 -27
  91. package/templates/skills/orc/examples/full-run-mock.md +2 -1
  92. package/templates/skills/orc/references/effort-and-mode.md +17 -17
  93. package/templates/skills/orc/references/preflight-report.md +3 -3
  94. package/templates/skills/orc/references/ultra-mode.md +1 -1
  95. package/templates/skills/orc/schemas/checkpoint.md +122 -122
  96. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
  97. package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
  98. package/templates/skills/orc-analyze/SKILL.md +4 -2
  99. package/templates/skills/orc-claude/SKILL.md +14 -14
  100. package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
  101. package/templates/skills/orc-diy/SKILL.md +5 -5
  102. package/templates/skills/orc-fast/SKILL.md +8 -6
  103. package/templates/skills/orc-fix/SKILL.md +110 -0
  104. package/templates/skills/orc-mini/SKILL.md +4 -4
  105. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
  106. package/templates/skills/orc-quick/README.md +1 -1
  107. package/templates/skills/orc-quick/SKILL.md +2 -2
  108. package/templates/skills/orc-quick/references/dispatch-gate.md +3 -2
  109. package/templates/skills/orc-quick/references/gh-mode.md +4 -2
  110. package/templates/skills/orc-retro/SKILL.md +1 -1
  111. package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
  112. package/templates/skills/orc-wiki/README.md +325 -325
  113. package/templates/skills/orc-wiki/SKILL.md +195 -195
  114. package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
  115. package/templates/skills/orc-wiki/references/extra.md +2 -2
  116. package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
  117. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
  118. package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
  119. package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
  120. package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
  121. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
@@ -1,235 +1,336 @@
1
- "use strict";
2
- /* panels/maintenance.js — orc ui client
3
- Preview-then-apply. The apply button stays disabled until a preview was
4
- fetched, the exact command is always visible, and a prune names EVERY file.
5
-
6
- Loaded by app.html in the order its numeric prefix names. Classic
7
- script, no import/export: an ES module import carries no query string,
8
- and every static request here needs the per-launch session token. */
9
-
10
- PANELS.maintenance = function (host) {
11
- head(host, t("maintenance.title"), t("maintenance.sub"));
12
- const body = el("div", "stack");
13
- host.append(body);
14
- renderMaintenance(body);
15
- };
16
-
17
- async function renderMaintenance(body) {
18
- body.replaceChildren(skeleton(5));
19
- const d = (await read("/api/maintenance")).data;
20
- const out = frag();
21
-
22
- out.append(el("div", "banner", t("maintenance.banner")));
23
-
24
- // ADVANCED is a section, not a disclaimer (v0.44.0). Everything above targets
25
- // THIS project; everything in the box below reaches outside it. The global
26
- // update lives there because a stale global install is a failure this panel
27
- // already reports and, until now, could only tell you to fix in a terminal.
28
- const advanced = card(t("maintenance.advanced"));
29
- advanced.id = "maintenance-advanced";
30
- advanced.append(el("div", "note", t("maintenance.advancedNote")));
31
- let anyAdvanced = false;
32
-
33
- for (const a of d.actions) {
34
- const row = el("div", "action");
35
- row.dataset.action = a.id; // the upgrade spotlight anchors on this
36
- const left = el("div");
37
- // Action id, label and command are the server's catalog — the label
38
- // describes an exact CLI invocation, so it is shown as written.
39
- left.append(el("div", "setting-name", a.id));
40
- left.append(el("div", "setting-desc", a.label));
41
- left.append(el("div", "action-cmd", a.command));
42
- if (a.network) left.append(el("div", "note", t("maintenance.network")));
43
-
44
- // `upgrade` is the one action whose whole point is a comparison, so it says
45
- // what it would actually do BEFORE you preview it — and offers to check
46
- // again, because "up to date" is only as old as the last check.
47
- if (a.id === "upgrade") {
48
- const status = el("div", "action-status");
49
- status.append(el("span", "note", t("maintenance.checking")));
50
- left.append(status);
51
- const paint = (v) => {
52
- const s = versionState(v);
53
- status.replaceChildren();
54
- status.append(chip(s.label, s.kind), el("span", "note", s.note));
55
- const again = el("button", "btn btn-ghost btn-sm btn-allow-busy", t("maintenance.checkAgain"));
56
- again.type = "button";
57
- again.addEventListener("click", () => {
58
- status.replaceChildren(el("span", "note", t("common.checking")));
59
- refreshVersion().then(paint).catch(() => status.replaceChildren(el("span", "note", t("maintenance.checkFailed"))));
60
- });
61
- status.append(again);
62
- };
63
- versionInfo().then(paint).catch(() => status.replaceChildren(el("span", "note", t("maintenance.couldNotCheck"))));
64
- }
65
-
66
- if (a.advanced) left.append(el("div", "note", t("maintenance.globalNote")));
67
-
68
- const btn = el("button", "btn btn-sm", t("maintenance.preview"));
69
- btn.type = "button";
70
- btn.addEventListener("click", () => previewAction(a.id, body));
71
- row.append(left, btn);
72
- if (a.advanced) {
73
- anyAdvanced = true;
74
- advanced.append(row);
75
- } else {
76
- out.append(row);
77
- }
78
- }
79
-
80
- if (anyAdvanced) out.append(advanced);
81
-
82
- const job = card(t("maintenance.lastRun"));
83
- job.id = "job-card";
84
- out.append(job);
85
- body.replaceChildren(out);
86
- refreshJob();
87
- }
88
-
89
- async function previewAction(action, body) {
90
- const d = (await read("/api/maintenance/preview?action=" + encodeURIComponent(action))).data;
91
- const b = el("div");
92
-
93
- b.append(el("div", null, d.label));
94
- b.append(el("div", "action-cmd", d.command));
95
- b.append(el("div", "note", t("maintenance.previewFrom", { command: d.preview_command })));
96
-
97
- // Said BEFORE the apply, not discovered after it. A panel that reloads itself
98
- // with no warning reads as a crash, and the whole point of this note is that
99
- // the reload is the thing the user wanted.
100
- if (d.restarts_ui) b.append(el("div", "note", t("maintenance.willRestart")));
101
-
102
- // The one action that writes outside this project says so before it runs, and
103
- // the preview it is showing came from the SAME target — `orc doctor --global`,
104
- // not the project doctor.
105
- if (d.advanced) {
106
- const g = el("div", "banner");
107
- g.append(el("div", null, t("maintenance.globalWarn")));
108
- b.append(g);
109
- }
110
-
111
- // Guard 1 — a run is mid-flight. Updating changes the skills that run will
112
- // resume into. The CLI has no idea you are mid-run; this panel does.
113
- let ackWaiting = !d.waiting_runs.length;
114
- if (d.waiting_runs.length) {
115
- const warn = el("div", "banner banner-bad");
116
- const inner = el("div");
117
- inner.append(el("strong", null, t("maintenance.waitingRuns", { n: d.waiting_runs.length })));
118
- inner.append(el("div", null, t("maintenance.waitingBody", { slugs: d.waiting_runs.join(", ") })));
119
- const lbl = el("label", "note");
120
- const cb = el("input");
121
- cb.type = "checkbox";
122
- cb.addEventListener("change", () => {
123
- ackWaiting = cb.checked;
124
- syncApply();
125
- });
126
- lbl.append(cb, document.createTextNode(" " + t("maintenance.waitingAck")));
127
- inner.append(lbl);
128
- warn.append(inner);
129
- b.append(warn);
130
- }
131
-
132
- // Guard 2 — a dirty working tree before an upgrade is worth a warning
133
- // BEFORE, not a surprise after.
134
- if (d.dirty_tree) {
135
- const warn = el("div", "banner");
136
- warn.append(el("div", null, t("maintenance.dirtyTree")));
137
- b.append(warn);
138
- }
139
-
140
- const pv = d.preview || {};
141
- if (action === "upgrade") {
142
- b.append(
143
- kvList([
144
- [t("maintenance.installed"), pv.version],
145
- [t("maintenance.available"), pv.latest || t("maintenance.availableUnknown")],
146
- [t("maintenance.source"), pv.install_spec],
147
- // The install URL and the URL the version number was read from are two
148
- // different things. Showing only the first is how "up to date" becomes
149
- // unfalsifiable from this panel.
150
- [t("maintenance.checkedAt"), pv.checked_ref || pv.checked_source || t("maintenance.availableUnknown")],
151
- [t("maintenance.updateAvailable"), pv.update_available ? t("maintenance.yes") : t("maintenance.no")],
152
- ])
153
- );
154
- b.append(el("div", "note", t("maintenance.upgradeNote")));
155
- } else {
156
- const findings = pv.findings || [];
157
- b.append(
158
- kvList([
159
- [t("maintenance.installedPayload"), pv.installed_version],
160
- [t("maintenance.thisCli"), pv.package_version],
161
- [t("maintenance.findings"), String(findings.length)],
162
- ])
163
- );
164
- if (!findings.length) b.append(el("div", "note", t("maintenance.healthy")));
165
- for (const f of findings) {
166
- const line = el("div", "note");
167
- line.append(
168
- chip(f.fixable ? t("overview.doctor.fixable") : t("overview.doctor.manual"), f.fixable ? "info" : "warn"),
169
- document.createTextNode(" " + f.message)
170
- );
171
- b.append(line);
172
- }
173
- // A count is NOT consent for a deletion — name every file.
174
- if (d.names_files) {
175
- const paths = findings.filter((f) => f.id === "orphan" || f.id === "orphan-candidates").flatMap((f) => f.paths || []);
176
- const c = el("div");
177
- c.append(el("div", "note", paths.length ? t("maintenance.wouldDelete", { n: paths.length }) : t("maintenance.wouldDeleteNone")));
178
- if (paths.length) {
179
- const fl = el("div", "file-list");
180
- for (const p of paths) fl.append(el("div", null, p));
181
- c.append(fl);
182
- }
183
- b.append(c);
184
- }
185
- }
186
-
187
- let applyBtn = null;
188
- const syncApply = () => {
189
- if (applyBtn) applyBtn.disabled = !ackWaiting;
190
- };
191
-
192
- const actions = [{ label: t("common.cancel"), onClick: (c) => c() }];
193
- if (action === "upgrade")
194
- actions.push({
195
- label: t("maintenance.checkOnly"),
196
- onClick: () => toast(t("maintenance.checkOnlyDone"), "ok"),
197
- });
198
- actions.push({
199
- label: action === "prune" ? t("maintenance.applyPrune") : action === "upgrade" ? t("maintenance.applyUpgrade") : t("maintenance.apply"),
200
- cls: action === "prune" || action === "upgrade" ? "btn-danger" : "btn-primary",
201
- id: "apply-btn",
202
- disabled: !ackWaiting,
203
- onClick: async (close) => {
204
- try {
205
- await post("/api/maintenance/apply", { action });
206
- close();
207
- toast(t("maintenance.started", { command: d.command }), "ok");
208
- setBusy(true);
209
- refreshJob();
210
- } catch (e) {
211
- toast(t("maintenance.startFail"), "bad", String(e.message));
212
- }
213
- },
214
- });
215
-
216
- modal({ title: t("maintenance.previewTitle", { command: d.command }), body: b, actions });
217
- applyBtn = document.getElementById("apply-btn");
218
- syncApply();
219
- }
220
-
221
- /* ============================================== PROMISES · BOUNDARY · SELF-SERVE
222
- (v0.46.0)
223
-
224
- THE LINE, restated because these three panels sit right on it: a FREE action
225
- gets a button, a PAID action gets a copy-able command. `orc pact check` runs
226
- the ledger's own cheap proofs and `orc handoff set` edits one graded surface —
227
- both deterministic, both a button. `/orc-pact`'s reconcile conversation and
228
- `/orc-boundary`'s evidence pass cost model tokens, so they are commands with
229
- the reason printed next to them.
230
-
231
- And the second rule, which these panels are the first real test of: the CLI's
232
- state words are the ONLY state words. HOLDING/DRIFTED/UNCHECKABLE/BROKEN and
233
- EXECUTE/ESCALATE/REFUSE are rendered verbatim, never softened into a friendlier
234
- synonym — a second vocabulary is drift no lint can see. test/webui.test.js
235
- greps this file for the literals that must come from the CLI instead. */
1
+ "use strict";
2
+ /* panels/maintenance.js — orc ui client
3
+ Preview-then-apply. The apply button stays disabled until a preview was
4
+ fetched, the exact command is always visible, and a prune names EVERY file.
5
+
6
+ Loaded by app.html in the order its numeric prefix names. Classic
7
+ script, no import/export: an ES module import carries no query string,
8
+ and every static request here needs the per-launch session token. */
9
+
10
+ PANELS.maintenance = function (host) {
11
+ head(host, t("maintenance.title"), t("maintenance.sub"));
12
+ const body = el("div", "stack");
13
+ host.append(body);
14
+ renderMaintenance(body);
15
+ };
16
+
17
+ async function renderMaintenance(body) {
18
+ body.replaceChildren(skeleton(5));
19
+ const d = (await read("/api/maintenance")).data;
20
+ const out = frag();
21
+
22
+ out.append(el("div", "banner", t("maintenance.banner")));
23
+
24
+ // ADVANCED is a section, not a disclaimer (v0.44.0). Everything above targets
25
+ // THIS project; everything in the box below reaches outside it. The global
26
+ // update lives there because a stale global install is a failure this panel
27
+ // already reports and, until now, could only tell you to fix in a terminal.
28
+ const advanced = card(t("maintenance.advanced"));
29
+ advanced.id = "maintenance-advanced";
30
+ advanced.append(el("div", "note", t("maintenance.advancedNote")));
31
+ let anyAdvanced = false;
32
+
33
+ for (const a of d.actions) {
34
+ const row = el("div", "action");
35
+ row.dataset.action = a.id; // the upgrade spotlight anchors on this
36
+ const left = el("div");
37
+ // Action id, label and command are the server's catalog — the label
38
+ // describes an exact CLI invocation, so it is shown as written.
39
+ left.append(el("div", "setting-name", a.id));
40
+ left.append(el("div", "setting-desc", a.label));
41
+ left.append(el("div", "action-cmd", a.command));
42
+ if (a.network) left.append(el("div", "note", t("maintenance.network")));
43
+
44
+ // `upgrade` is the one action whose whole point is a comparison, so it says
45
+ // what it would actually do BEFORE you preview it — and offers to check
46
+ // again, because "up to date" is only as old as the last check.
47
+ if (a.id === "upgrade") {
48
+ const status = el("div", "action-status");
49
+ status.append(el("span", "note", t("maintenance.checking")));
50
+ left.append(status);
51
+ const paint = (v) => {
52
+ const s = versionState(v);
53
+ status.replaceChildren();
54
+ status.append(chip(s.label, s.kind), el("span", "note", s.note));
55
+ const again = el("button", "btn btn-ghost btn-sm btn-allow-busy", t("maintenance.checkAgain"));
56
+ again.type = "button";
57
+ again.addEventListener("click", () => {
58
+ status.replaceChildren(el("span", "note", t("common.checking")));
59
+ refreshVersion().then(paint).catch(() => status.replaceChildren(el("span", "note", t("maintenance.checkFailed"))));
60
+ });
61
+ status.append(again);
62
+ };
63
+ versionInfo().then(paint).catch(() => status.replaceChildren(el("span", "note", t("maintenance.couldNotCheck"))));
64
+ }
65
+
66
+ // v2.1.0 W7 — the clear-logs row says WHICH age it deletes at and when the
67
+ // automatic sweep last ran. Every number is the CLI's (`clear logs --summary`).
68
+ if (a.id === "clear-logs" && a.status) left.append(clearLogsStatus(a.status));
69
+
70
+ if (a.advanced) left.append(el("div", "note", t("maintenance.globalNote")));
71
+
72
+ const btn = el("button", "btn btn-sm", t("maintenance.preview"));
73
+ btn.type = "button";
74
+ btn.addEventListener("click", () => previewAction(a.id, body));
75
+ row.append(left, btn);
76
+ if (a.advanced) {
77
+ anyAdvanced = true;
78
+ advanced.append(row);
79
+ } else {
80
+ out.append(row);
81
+ }
82
+ }
83
+
84
+ if (anyAdvanced) out.append(advanced);
85
+
86
+ const job = card(t("maintenance.lastRun"));
87
+ job.id = "job-card";
88
+ out.append(job);
89
+ body.replaceChildren(out);
90
+ refreshJob();
91
+ }
92
+
93
+ // "Older than 90 days (Settings ▸ log retention). Last automatic sweep: …" —
94
+ // the day value links to the Settings row that sets it.
95
+ function clearLogsStatus(s) {
96
+ const box = el("div", "note");
97
+ const [pre, post] = t("maintenance.clearLogs.olderThan", { days: "\u0000" }).split("\u0000");
98
+ const link = el("a", null, t("maintenance.clearLogs.days", { n: s.older_than_days }));
99
+ link.href = "#/settings?key=log_retention_days";
100
+ box.append(document.createTextNode(pre || ""), link, document.createTextNode(post || ""));
101
+ box.append(document.createTextNode(" "));
102
+ box.append(
103
+ document.createTextNode(
104
+ s.last_sweep
105
+ ? t("maintenance.clearLogs.lastSweep", { date: s.last_sweep.date, files: s.last_sweep.files })
106
+ : s.auto === "on"
107
+ ? t("maintenance.clearLogs.noSweepYet")
108
+ : t("maintenance.clearLogs.autoOff")
109
+ )
110
+ );
111
+ if (s.pruned_runs) box.append(document.createTextNode(" " + t("maintenance.clearLogs.pruned", { n: s.pruned_runs })));
112
+ return box;
113
+ }
114
+
115
+ // The third preview shape (v2.1.0 W7): `orc clear logs --json`. A table of
116
+ // what it would delete, the kept list with its reasons (collapsed), the totals
117
+ // and the old default folder. Rendered as the CLI wrote it.
118
+ function clearLogsPreview(b, pv) {
119
+ b.append(
120
+ kvList([
121
+ [t("maintenance.clearLogs.cutoff"), `${pv.cutoff} (${t("maintenance.clearLogs.days", { n: pv.effective_days })})`],
122
+ ["log_dir", pv.log_dir],
123
+ ["run_dir", pv.run_dir],
124
+ [t("maintenance.clearLogs.totals"), t("maintenance.clearLogs.totalsValue", { units: (pv.totals || {}).units || 0, files: (pv.totals || {}).files || 0, bytes: (pv.totals || {}).bytes || 0 })],
125
+ ])
126
+ );
127
+ // The CLI's own sentences (cutoff moved, the old default folder) — shown as written.
128
+ for (const n of pv.notes || []) b.append(el("div", "note", n));
129
+ const del = pv.delete || [];
130
+ if (!del.length) b.append(el("div", "note", t("maintenance.clearLogs.none")));
131
+ else {
132
+ b.append(el("div", "note", t("maintenance.wouldDelete", { n: (pv.totals || {}).files || 0 })));
133
+ const tbl = el("table", "md-table");
134
+ const hr = el("tr");
135
+ for (const h of [t("maintenance.clearLogs.colPath"), t("maintenance.clearLogs.colKind"), t("maintenance.clearLogs.colLast"), t("maintenance.clearLogs.colBytes")]) hr.append(el("th", null, h));
136
+ tbl.append(hr);
137
+ for (const d of del) {
138
+ const tr = el("tr");
139
+ const p = el("td");
140
+ // EVERY file is named — a unit is several files, and a count is not consent.
141
+ const fl = el("div", "file-list");
142
+ for (const f of d.files || [d.path]) fl.append(el("div", null, f));
143
+ if (d.ref) fl.append(el("div", null, "git ref " + d.ref));
144
+ p.append(fl);
145
+ tr.append(p, el("td", null, d.kind), el("td", null, d.last_activity), el("td", null, String(d.bytes)));
146
+ tbl.append(tr);
147
+ }
148
+ b.append(tbl);
149
+ }
150
+ const keep = pv.keep || [];
151
+ if (keep.length) {
152
+ const det = el("details");
153
+ det.append(el("summary", null, t("maintenance.clearLogs.kept", { n: keep.length })));
154
+ const fl = el("div", "file-list");
155
+ for (const k of keep) fl.append(el("div", null, `${k.path} — ${k.reason}`));
156
+ det.append(fl);
157
+ b.append(det);
158
+ }
159
+ if (pv.default_dir)
160
+ b.append(
161
+ el(
162
+ "div",
163
+ "note",
164
+ t(pv.default_dir.included ? "maintenance.clearLogs.defaultIncluded" : "maintenance.clearLogs.defaultDir", {
165
+ path: pv.default_dir.path,
166
+ files: pv.default_dir.files,
167
+ bytes: pv.default_dir.bytes,
168
+ })
169
+ )
170
+ );
171
+ }
172
+
173
+ async function previewAction(action, body) {
174
+ const d = (await read("/api/maintenance/preview?action=" + encodeURIComponent(action))).data;
175
+ const b = el("div");
176
+
177
+ b.append(el("div", null, d.label));
178
+ b.append(el("div", "action-cmd", d.command));
179
+ b.append(el("div", "note", t("maintenance.previewFrom", { command: d.preview_command })));
180
+
181
+ // Said BEFORE the apply, not discovered after it. A panel that reloads itself
182
+ // with no warning reads as a crash, and the whole point of this note is that
183
+ // the reload is the thing the user wanted.
184
+ if (d.restarts_ui) b.append(el("div", "note", t("maintenance.willRestart")));
185
+
186
+ // The one action that writes outside this project says so before it runs, and
187
+ // the preview it is showing came from the SAME target — `orc doctor --global`,
188
+ // not the project doctor.
189
+ if (d.advanced) {
190
+ const g = el("div", "banner");
191
+ g.append(el("div", null, t("maintenance.globalWarn")));
192
+ b.append(g);
193
+ }
194
+
195
+ // Guard 1 — a run is mid-flight. Updating changes the skills that run will
196
+ // resume into. The CLI has no idea you are mid-run; this panel does.
197
+ let ackWaiting = !d.waiting_runs.length;
198
+ if (d.waiting_runs.length) {
199
+ const warn = el("div", "banner banner-bad");
200
+ const inner = el("div");
201
+ inner.append(el("strong", null, t("maintenance.waitingRuns", { n: d.waiting_runs.length })));
202
+ inner.append(
203
+ el(
204
+ "div",
205
+ null,
206
+ action === "clear-logs"
207
+ ? t("maintenance.clearLogs.waitingBody", { slugs: d.waiting_runs.join(", ") })
208
+ : t("maintenance.waitingBody", { slugs: d.waiting_runs.join(", ") })
209
+ )
210
+ );
211
+ const lbl = el("label", "note");
212
+ const cb = el("input");
213
+ cb.type = "checkbox";
214
+ cb.addEventListener("change", () => {
215
+ ackWaiting = cb.checked;
216
+ syncApply();
217
+ });
218
+ lbl.append(cb, document.createTextNode(" " + t("maintenance.waitingAck")));
219
+ inner.append(lbl);
220
+ warn.append(inner);
221
+ b.append(warn);
222
+ }
223
+
224
+ // Guard 2 — a dirty working tree before an upgrade is worth a warning
225
+ // BEFORE, not a surprise after.
226
+ if (d.dirty_tree) {
227
+ const warn = el("div", "banner");
228
+ warn.append(el("div", null, t("maintenance.dirtyTree")));
229
+ b.append(warn);
230
+ }
231
+
232
+ const pv = d.preview || {};
233
+ if (action === "clear-logs") {
234
+ clearLogsPreview(b, pv);
235
+ } else if (action === "upgrade") {
236
+ b.append(
237
+ kvList([
238
+ [t("maintenance.installed"), pv.version],
239
+ [t("maintenance.available"), pv.latest || t("maintenance.availableUnknown")],
240
+ [t("maintenance.source"), pv.install_spec],
241
+ // The install URL and the URL the version number was read from are two
242
+ // different things. Showing only the first is how "up to date" becomes
243
+ // unfalsifiable from this panel.
244
+ [t("maintenance.checkedAt"), pv.checked_ref || pv.checked_source || t("maintenance.availableUnknown")],
245
+ [t("maintenance.updateAvailable"), pv.update_available ? t("maintenance.yes") : t("maintenance.no")],
246
+ ])
247
+ );
248
+ b.append(el("div", "note", t("maintenance.upgradeNote")));
249
+ } else {
250
+ const findings = pv.findings || [];
251
+ b.append(
252
+ kvList([
253
+ [t("maintenance.installedPayload"), pv.installed_version],
254
+ [t("maintenance.thisCli"), pv.package_version],
255
+ [t("maintenance.findings"), String(findings.length)],
256
+ ])
257
+ );
258
+ if (!findings.length) b.append(el("div", "note", t("maintenance.healthy")));
259
+ for (const f of findings) {
260
+ const line = el("div", "note");
261
+ line.append(
262
+ chip(f.fixable ? t("overview.doctor.fixable") : t("overview.doctor.manual"), f.fixable ? "info" : "warn"),
263
+ document.createTextNode(" " + f.message)
264
+ );
265
+ b.append(line);
266
+ }
267
+ // A count is NOT consent for a deletion — name every file.
268
+ if (d.names_files) {
269
+ const paths = findings.filter((f) => f.id === "orphan" || f.id === "orphan-candidates").flatMap((f) => f.paths || []);
270
+ const c = el("div");
271
+ c.append(el("div", "note", paths.length ? t("maintenance.wouldDelete", { n: paths.length }) : t("maintenance.wouldDeleteNone")));
272
+ if (paths.length) {
273
+ const fl = el("div", "file-list");
274
+ for (const p of paths) fl.append(el("div", null, p));
275
+ c.append(fl);
276
+ }
277
+ b.append(c);
278
+ }
279
+ }
280
+
281
+ let applyBtn = null;
282
+ const syncApply = () => {
283
+ if (applyBtn) applyBtn.disabled = !ackWaiting;
284
+ };
285
+
286
+ const actions = [{ label: t("common.cancel"), onClick: (c) => c() }];
287
+ if (action === "upgrade")
288
+ actions.push({
289
+ label: t("maintenance.checkOnly"),
290
+ onClick: () => toast(t("maintenance.checkOnlyDone"), "ok"),
291
+ });
292
+ actions.push({
293
+ label:
294
+ action === "prune"
295
+ ? t("maintenance.applyPrune")
296
+ : action === "upgrade"
297
+ ? t("maintenance.applyUpgrade")
298
+ : action === "clear-logs"
299
+ ? t("maintenance.clearLogs.apply")
300
+ : t("maintenance.apply"),
301
+ cls: action === "prune" || action === "upgrade" || d.danger ? "btn-danger" : "btn-primary",
302
+ id: "apply-btn",
303
+ disabled: !ackWaiting,
304
+ onClick: async (close) => {
305
+ try {
306
+ await post("/api/maintenance/apply", { action });
307
+ close();
308
+ toast(t("maintenance.started", { command: d.command }), "ok");
309
+ setBusy(true);
310
+ refreshJob();
311
+ } catch (e) {
312
+ toast(t("maintenance.startFail"), "bad", String(e.message));
313
+ }
314
+ },
315
+ });
316
+
317
+ modal({ title: t("maintenance.previewTitle", { command: d.command }), body: b, actions });
318
+ applyBtn = document.getElementById("apply-btn");
319
+ syncApply();
320
+ }
321
+
322
+ /* ============================================== PROMISES · BOUNDARY · SELF-SERVE
323
+ (v0.46.0)
324
+
325
+ THE LINE, restated because these three panels sit right on it: a FREE action
326
+ gets a button, a PAID action gets a copy-able command. `orc pact check` runs
327
+ the ledger's own cheap proofs and `orc handoff set` edits one graded surface —
328
+ both deterministic, both a button. `/orc-pact`'s reconcile conversation and
329
+ `/orc-boundary`'s evidence pass cost model tokens, so they are commands with
330
+ the reason printed next to them.
331
+
332
+ And the second rule, which these panels are the first real test of: the CLI's
333
+ state words are the ONLY state words. HOLDING/DRIFTED/UNCHECKABLE/BROKEN and
334
+ EXECUTE/ESCALATE/REFUSE are rendered verbatim, never softened into a friendlier
335
+ synonym — a second vocabulary is drift no lint can see. test/webui.test.js
336
+ greps this file for the literals that must come from the CLI instead. */