@worca/app 1.3.0 → 1.4.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 (187) hide show
  1. package/README.md +85 -6
  2. package/agents/clarify.meta.json +1 -0
  3. package/agents/memoryDefragmenter.meta.json +2 -1
  4. package/agents/reviewer.meta.json +60 -0
  5. package/agents/worca-cc-code-reviewer.md +33 -0
  6. package/agents/worca-cc-memory-defragmenter.md +5 -3
  7. package/agents/workspaceScanner.meta.json +1 -0
  8. package/package.json +14 -10
  9. package/scripts/git-diff.mjs +25 -0
  10. package/scripts/gitDiff.meta.json +18 -0
  11. package/scripts/js-inline.mjs +11 -0
  12. package/scripts/js.meta.json +22 -0
  13. package/scripts/py-inline.py +27 -0
  14. package/scripts/py.meta.json +22 -0
  15. package/scripts/shell.meta.json +24 -0
  16. package/skills/worca/SKILL.md +3 -2
  17. package/src/cli/models.mjs +247 -0
  18. package/src/cli/render.mjs +72 -4
  19. package/src/cli/schedule.mjs +494 -0
  20. package/src/cli/worca-cc.mjs +1001 -22
  21. package/src/core/agent-registry.mjs +75 -23
  22. package/src/core/agent-store.mjs +51 -2
  23. package/src/core/artifacts.mjs +73 -10
  24. package/src/core/ask/events.mjs +119 -1
  25. package/src/core/ask/limits.mjs +32 -4
  26. package/src/core/ask/mcp-stdio.mjs +12 -0
  27. package/src/core/ask/model-deps.mjs +126 -0
  28. package/src/core/ask/model-proposal.mjs +370 -0
  29. package/src/core/ask/models.mjs +12 -0
  30. package/src/core/ask/policy-deps.mjs +124 -0
  31. package/src/core/ask/policy-proposal.mjs +363 -0
  32. package/src/core/ask/prompt.mjs +74 -9
  33. package/src/core/ask/proposal.mjs +54 -5
  34. package/src/core/ask/schedule-deps.mjs +83 -0
  35. package/src/core/ask/schedule-spec.mjs +310 -0
  36. package/src/core/ask/script-deps.mjs +357 -0
  37. package/src/core/ask/source-deps.mjs +52 -0
  38. package/src/core/ask/source-spec.mjs +157 -0
  39. package/src/core/ask/spawn.mjs +1 -0
  40. package/src/core/ask/store.mjs +6 -3
  41. package/src/core/ask/tool-deps.mjs +4 -0
  42. package/src/core/ask/tools.mjs +657 -37
  43. package/src/core/ask/turn.mjs +109 -2
  44. package/src/core/ask-files.mjs +406 -0
  45. package/src/core/ask-forms.mjs +195 -0
  46. package/src/core/ask-projection.mjs +72 -0
  47. package/src/core/bridge/errors.mjs +84 -0
  48. package/src/core/bridge/provider-ops.mjs +281 -0
  49. package/src/core/bridge/providers/copilot.mjs +269 -0
  50. package/src/core/bridge/providers/endpoint.mjs +257 -0
  51. package/src/core/bridge/registry.mjs +88 -0
  52. package/src/core/bridge/semaphore.mjs +73 -0
  53. package/src/core/bridge/server.mjs +184 -0
  54. package/src/core/bridge/telemetry.mjs +53 -0
  55. package/src/core/bridge/translate/request.mjs +252 -0
  56. package/src/core/bridge/translate/response.mjs +82 -0
  57. package/src/core/bridge/translate/stream.mjs +242 -0
  58. package/src/core/bridge/upstream.mjs +209 -0
  59. package/src/core/chat/command-router.mjs +58 -4
  60. package/src/core/chat/notifier.mjs +14 -1
  61. package/src/core/chat/renderers.mjs +35 -0
  62. package/src/core/claude-runner.mjs +126 -19
  63. package/src/core/config.mjs +212 -31
  64. package/src/core/cost-budget.mjs +3 -2
  65. package/src/core/db.mjs +169 -15
  66. package/src/core/failure-policy.mjs +10 -0
  67. package/src/core/fs-browse.mjs +16 -4
  68. package/src/core/git-info.mjs +22 -0
  69. package/src/core/graph/builtin-workflows.mjs +3 -1
  70. package/src/core/graph/exec-io.mjs +71 -0
  71. package/src/core/graph/executor.mjs +139 -70
  72. package/src/core/graph/human-evidence.mjs +131 -0
  73. package/src/core/graph/python-probe.mjs +172 -0
  74. package/src/core/graph/registry-ports.mjs +10 -6
  75. package/src/core/graph/scheduler.mjs +39 -24
  76. package/src/core/graph/script-child.mjs +81 -0
  77. package/src/core/graph/script-runner.mjs +597 -0
  78. package/src/core/graph/worca_script.py +207 -0
  79. package/src/core/guardrail-store.mjs +16 -0
  80. package/src/core/human-backfill.mjs +108 -0
  81. package/src/core/human-rate.mjs +17 -0
  82. package/src/core/index-html.mjs +6 -2
  83. package/src/core/memory-defrag-model.mjs +112 -0
  84. package/src/core/memory-store.mjs +70 -18
  85. package/src/core/memory-sync.mjs +22 -11
  86. package/src/core/metrics/read.mjs +4 -1
  87. package/src/core/metrics/record.mjs +50 -2
  88. package/src/core/metrics/sync.mjs +6 -4
  89. package/src/core/model-env.mjs +149 -0
  90. package/src/core/model-test.mjs +14 -1
  91. package/src/core/notifications.mjs +128 -0
  92. package/src/core/onboarding.mjs +8 -2
  93. package/src/core/orchestrator.mjs +357 -26
  94. package/src/core/phases.mjs +95 -6
  95. package/src/core/plugin-api.mjs +24 -7
  96. package/src/core/plugin-manifest.mjs +184 -18
  97. package/src/core/plugin-models.mjs +1 -0
  98. package/src/core/plugin-script-cases.mjs +118 -0
  99. package/src/core/plugin-store.mjs +163 -17
  100. package/src/core/plugin-workflows.mjs +71 -17
  101. package/src/core/policy/cache.mjs +116 -0
  102. package/src/core/policy/effective.mjs +175 -0
  103. package/src/core/policy/gate.mjs +91 -0
  104. package/src/core/policy/local.mjs +145 -0
  105. package/src/core/policy/registry.mjs +330 -0
  106. package/src/core/policy/scope.mjs +61 -0
  107. package/src/core/policy/state.mjs +79 -0
  108. package/src/core/policy/sync.mjs +513 -0
  109. package/src/core/protocol.mjs +43 -0
  110. package/src/core/run-harness.mjs +421 -72
  111. package/src/core/scheduler.mjs +980 -0
  112. package/src/core/script-bench.mjs +628 -0
  113. package/src/core/script-registry.mjs +116 -0
  114. package/src/core/script-store.mjs +563 -0
  115. package/src/core/settings.mjs +489 -14
  116. package/src/core/stats.mjs +33 -2
  117. package/src/core/workflow-export.mjs +94 -3
  118. package/src/core/workflow-share.mjs +67 -18
  119. package/src/core/workflows.mjs +47 -11
  120. package/src/core/workspaces.mjs +18 -12
  121. package/src/shared/forms/answer.mjs +164 -0
  122. package/src/shared/forms/catalog.mjs +91 -0
  123. package/src/shared/forms/form-def.mjs +290 -0
  124. package/src/shared/forms/layout.mjs +67 -0
  125. package/src/shared/forms/paths.mjs +47 -0
  126. package/src/shared/forms/project.mjs +309 -0
  127. package/src/shared/forms/schema.mjs +205 -0
  128. package/src/shared/graph/agent-meta.mjs +55 -5
  129. package/src/shared/graph/constants.mjs +14 -2
  130. package/src/shared/graph/flow-layout.mjs +2 -1
  131. package/src/shared/graph/isomorphic.mjs +5 -3
  132. package/src/shared/graph/manifest.mjs +22 -13
  133. package/src/shared/graph/ports.mjs +45 -19
  134. package/src/shared/graph/script-cases.mjs +257 -0
  135. package/src/shared/graph/script-icons.mjs +46 -0
  136. package/src/shared/graph/script-infer.mjs +259 -0
  137. package/src/shared/graph/script-meta.mjs +408 -0
  138. package/src/shared/graph/script-templates.mjs +201 -0
  139. package/src/shared/graph/template.mjs +4 -4
  140. package/src/shared/graph/validate.mjs +89 -16
  141. package/src/shared/human-estimate.mjs +100 -0
  142. package/src/shared/schedule/recurrence.mjs +353 -0
  143. package/src/shared/team-metrics/aggregate.mjs +51 -11
  144. package/{scripts → tools}/install.mjs +3 -3
  145. package/ui/public/app.js +4390 -683
  146. package/ui/public/artifact-picker.mjs +189 -0
  147. package/ui/public/ask/dom.mjs +121 -0
  148. package/ui/public/ask/form-preview.mjs +55 -0
  149. package/ui/public/ask/form-renderer.mjs +250 -0
  150. package/ui/public/ask/registry.mjs +53 -0
  151. package/ui/public/ask/widgets-display.mjs +370 -0
  152. package/ui/public/ask/widgets-input.mjs +624 -0
  153. package/ui/public/ask/widgets-layout.mjs +90 -0
  154. package/ui/public/ask-panel.mjs +401 -27
  155. package/ui/public/ask-run-card.mjs +1 -1
  156. package/ui/public/bridge-view.mjs +694 -0
  157. package/ui/public/chat-settings-view.mjs +24 -0
  158. package/ui/public/code-editor.mjs +181 -0
  159. package/ui/public/getting-started.mjs +34 -7
  160. package/ui/public/graph/composer.mjs +138 -10
  161. package/ui/public/graph/inspector.mjs +61 -58
  162. package/ui/public/graph/palette.mjs +27 -9
  163. package/ui/public/graph/run-decor.mjs +34 -17
  164. package/ui/public/graph/run-hosts.mjs +7 -1
  165. package/ui/public/graph/save-dialog.mjs +3 -0
  166. package/ui/public/graph/view.mjs +23 -7
  167. package/ui/public/guardrails-view.mjs +15 -3
  168. package/ui/public/guide-spot.mjs +87 -9
  169. package/ui/public/index.html +480 -141
  170. package/ui/public/memory-view.mjs +22 -4
  171. package/ui/public/models-view.mjs +162 -17
  172. package/ui/public/node-tunables.mjs +33 -4
  173. package/ui/public/plugins-view.mjs +23 -1
  174. package/ui/public/results-view.mjs +4 -2
  175. package/ui/public/schedule-sheet.mjs +430 -0
  176. package/ui/public/schedules-view.mjs +432 -0
  177. package/ui/public/script-bench-view.mjs +1154 -0
  178. package/ui/public/script-forms.mjs +282 -0
  179. package/ui/public/script-wizard.mjs +529 -0
  180. package/ui/public/scripts-view.mjs +868 -0
  181. package/ui/public/stats-view.mjs +159 -52
  182. package/ui/public/style.css +1461 -44
  183. package/ui/public/team-metrics-surfaces.mjs +77 -16
  184. package/ui/public/team-metrics-view.mjs +68 -5
  185. package/ui/public/team-policy-view.mjs +1402 -0
  186. package/ui/public/ui-level.mjs +237 -0
  187. package/ui/server.mjs +1827 -73
@@ -0,0 +1,237 @@
1
+ // ui-level.mjs — the interface mode (docs/ui-levels.md): simple | advanced | expert.
2
+ //
3
+ // One machine-wide VIEW preference deciding how much of the UI is on screen. It
4
+ // is never a permission: deep links still resolve, nothing is disabled. The
5
+ // server renders the stored mode into <html data-level> (src/core/index-html.mjs)
6
+ // so the first paint is already right; this module keeps it live.
7
+ //
8
+ // How an element gets a level:
9
+ // markup <button data-min-level="advanced"> (style.css hides it below that)
10
+ // renderer tagLevel(el, 'expert') (same attribute, from JS)
11
+ // logic if (levelAtLeast('advanced')) … (option lists, default tabs)
12
+ // keep keepVisible(el, true) (a non-default value stays on screen)
13
+ //
14
+ // Levels are cumulative: advanced shows everything simple shows, expert shows all.
15
+
16
+ export const UI_LEVELS = Object.freeze(['simple', 'advanced', 'expert']);
17
+ export const DEFAULT_UI_LEVEL = 'expert'; // no attribute (tests, an old shell) ⇒ gate nothing
18
+
19
+ export const LEVEL_INFO = Object.freeze({
20
+ simple: Object.freeze({
21
+ label: 'Simple',
22
+ who: 'New to Worca',
23
+ desc: 'Pick a project, describe the task, watch the run, answer its questions and read the result. Nothing here can break a run.',
24
+ adds: 'Shows: New pipeline, Running, History, Projects, budget limits and Ask Worca.',
25
+ }),
26
+ advanced: Object.freeze({
27
+ label: 'Advanced',
28
+ who: 'Regular use',
29
+ desc: 'Control how a run executes and review what it changed: branches, guardrails, per-agent models, the diff, pull requests and your own workflows.',
30
+ adds: 'Adds: Statistics, Workflow Composer, Workspaces, plugins, memory and the live log.',
31
+ }),
32
+ expert: Object.freeze({
33
+ label: 'Expert',
34
+ who: 'Authoring and team setup',
35
+ desc: 'Everything. Author agents, models and guardrail sets, tune fan-out and loop limits, filter logs by execution and run team metrics.',
36
+ adds: 'Adds: Agents, Team metrics, Models, diagnostics and every per-node tunable.',
37
+ }),
38
+ });
39
+
40
+ export function isUiLevel(v) { return UI_LEVELS.includes(v); }
41
+ export function normalizeLevel(v) { return isUiLevel(v) ? v : DEFAULT_UI_LEVEL; }
42
+ export function levelRank(v) { return UI_LEVELS.indexOf(normalizeLevel(v)); }
43
+
44
+ const docOf = (doc) => doc || globalThis.document;
45
+
46
+ /** The mode the page is in right now. */
47
+ export function currentLevel(doc) {
48
+ const d = docOf(doc);
49
+ return normalizeLevel(d && d.documentElement ? d.documentElement.dataset.level : undefined);
50
+ }
51
+
52
+ /** True when the current mode shows things of level `min`. */
53
+ export function levelAtLeast(min, doc) { return levelRank(currentLevel(doc)) >= levelRank(min); }
54
+
55
+ /** True when `min` is shown at `level` (pure; no document). */
56
+ export function levelShows(level, min) { return levelRank(level) >= levelRank(min); }
57
+
58
+ /** Mark `el` as belonging to `min` and above. Returns `el` so it chains in a renderer. */
59
+ export function tagLevel(el, min) {
60
+ if (el && isUiLevel(min)) el.dataset.minLevel = min;
61
+ return el;
62
+ }
63
+
64
+ /** A gated control holding a non-default value stays visible at every level. */
65
+ export function keepVisible(el, on) {
66
+ if (!el) return el;
67
+ if (on) el.dataset.levelKeep = '1'; else delete el.dataset.levelKeep;
68
+ return el;
69
+ }
70
+
71
+ /** The lowest mode that shows `el`: the highest data-min-level on it or an ancestor. */
72
+ export function minLevelFor(el) {
73
+ let best = 'simple';
74
+ for (let n = el; n && n.dataset; n = n.parentElement) {
75
+ if (n.dataset.levelKeep) continue;
76
+ const m = n.dataset.minLevel;
77
+ if (isUiLevel(m) && levelRank(m) > levelRank(best)) best = m;
78
+ }
79
+ return best;
80
+ }
81
+
82
+ // The icon IS the state readout (the collapsed rail shows nothing else): a stack
83
+ // of layers, the top sheet always solid, the second lit from advanced, the third
84
+ // from expert. Same 24px grid and stroke as every other nav icon.
85
+ export function levelIconSvg(level) {
86
+ const r = levelRank(level);
87
+ const off = (n) => (r < n ? ' class="lv-off"' : '');
88
+ return '<svg class="lv-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" '
89
+ + 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
90
+ + '<path d="M12 3 3 8l9 5 9-5-9-5z"></path>'
91
+ + `<path${off(1)} d="M3 12.5l9 5 9-5"></path>`
92
+ + `<path${off(2)} d="M3 17l9 5 9-5"></path></svg>`;
93
+ }
94
+
95
+ /** Write the mode onto <html> and tell the app (renderers that branch on it repaint). */
96
+ export function applyLevel(level, doc) {
97
+ const d = docOf(doc);
98
+ const next = normalizeLevel(level);
99
+ const prev = d.documentElement.dataset.level;
100
+ d.documentElement.dataset.level = next;
101
+ if (prev !== next) {
102
+ const Ev = (d.defaultView && d.defaultView.CustomEvent) || globalThis.CustomEvent; // jsdom rejects Node's
103
+ d.dispatchEvent(new Ev('worca:level', { detail: { level: next, previous: prev || null } }));
104
+ }
105
+ return next;
106
+ }
107
+
108
+ const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
109
+
110
+ /** The three radio cards of the dialog. */
111
+ export function levelCardsHtml(level) {
112
+ const cur = normalizeLevel(level);
113
+ return UI_LEVELS.map((id) => {
114
+ const i = LEVEL_INFO[id];
115
+ const on = id === cur;
116
+ return `<button type="button" class="lv-card" role="radio" data-level-choice="${id}" aria-checked="${on}" tabindex="${on ? 0 : -1}">`
117
+ + `<span class="lv-card-icon">${levelIconSvg(id)}</span>`
118
+ + `<span class="lv-card-text"><span class="lv-card-title">${esc(i.label)}<span class="lv-card-who">${esc(i.who)}</span></span>`
119
+ + `<span class="lv-card-desc">${esc(i.desc)}</span><span class="lv-card-adds">${esc(i.adds)}</span></span>`
120
+ + '<span class="lv-card-radio" aria-hidden="true"></span></button>';
121
+ }).join('');
122
+ }
123
+
124
+ /**
125
+ * Wire the mode item(s), the dialog and the Settings card.
126
+ *
127
+ * @param {object} o
128
+ * @param {Document} [o.doc]
129
+ * @param {(level:string)=>Promise<{ok:boolean,level?:string,error?:string}>} o.save persists; answers the CONFIRMED mode
130
+ * @returns {{paint:(level?:string)=>void, open:(from?:Element)=>void, close:()=>void, choose:(level:string)=>Promise<void>}}
131
+ */
132
+ export function createLevelController({ doc, save } = {}) {
133
+ const d = docOf(doc);
134
+ const $ = (s) => d.querySelector(s);
135
+ const modal = $('#mode-modal');
136
+ const cards = $('#mode-cards');
137
+ const msg = $('#mode-msg');
138
+ let confirmed = currentLevel(d); // the last SERVER-confirmed mode
139
+ let seq = 0; // out-of-order saves never repaint a stale answer
140
+ let opener = null;
141
+
142
+ function paint(level) {
143
+ const lvl = applyLevel(level === undefined ? currentLevel(d) : level, d);
144
+ const info = LEVEL_INFO[lvl];
145
+ for (const b of d.querySelectorAll('[data-mode-open]')) {
146
+ const icon = b.querySelector('.lv-icon-slot');
147
+ if (icon) icon.innerHTML = levelIconSvg(lvl);
148
+ const name = b.querySelector('.lv-name');
149
+ if (name) name.textContent = info.label; // just the level: the icon says what it is
150
+ b.setAttribute('aria-label', `Interface mode: ${info.label}. Change how much of Worca is shown`);
151
+ b.title = `${info.label} — change how much of Worca is shown`; // the collapsed rail's only label
152
+ }
153
+ const sIcon = $('#modeSettingsIcon'); if (sIcon) sIcon.innerHTML = levelIconSvg(lvl);
154
+ const sName = $('#modeSettingsName'); if (sName) sName.textContent = info.label;
155
+ const sDesc = $('#modeSettingsDesc'); if (sDesc) sDesc.textContent = info.desc;
156
+ if (cards && modal && !modal.classList.contains('hidden')) {
157
+ const had = d.activeElement && d.activeElement.closest && d.activeElement.closest('#mode-cards');
158
+ cards.innerHTML = levelCardsHtml(lvl);
159
+ if (had) { const b = cards.querySelector('[aria-checked="true"]'); if (b) b.focus(); }
160
+ }
161
+ }
162
+
163
+ function open(from, { keepMsg = false } = {}) {
164
+ if (!modal) return;
165
+ opener = from || d.activeElement || null;
166
+ if (msg && !keepMsg) { msg.textContent = ''; msg.className = 'hint'; }
167
+ cards.innerHTML = levelCardsHtml(currentLevel(d));
168
+ modal.classList.remove('hidden');
169
+ const b = cards.querySelector('[aria-checked="true"]'); if (b) b.focus();
170
+ }
171
+ function close() {
172
+ if (!modal || modal.classList.contains('hidden')) return;
173
+ modal.classList.add('hidden');
174
+ // The opener may have been repainted or hidden by the change; fall back to the sidebar item.
175
+ const back = (opener && opener.isConnected && opener.offsetParent !== null) ? opener : $('.nav [data-mode-open]');
176
+ if (back && typeof back.focus === 'function') back.focus();
177
+ opener = null;
178
+ }
179
+
180
+ async function choose(level) {
181
+ if (!isUiLevel(level)) return;
182
+ const mine = ++seq;
183
+ const previous = confirmed;
184
+ paint(level); // optimistic: the app re-lays out behind the dialog
185
+ if (msg) { msg.textContent = ''; msg.className = 'hint'; }
186
+ let out;
187
+ try { out = await save(level); } catch (e) { out = { ok: false, error: (e && e.message) || 'network error' }; }
188
+ if (mine !== seq) return; // a later click owns the paint now
189
+ if (!out || !out.ok) {
190
+ paint(previous);
191
+ if (msg) { msg.textContent = `Could not save the mode: ${(out && out.error) || 'unknown error'}`; msg.className = 'hint err'; }
192
+ return;
193
+ }
194
+ confirmed = normalizeLevel(out.level || level);
195
+ paint(confirmed);
196
+ }
197
+
198
+ d.addEventListener('click', (e) => {
199
+ const t = e.target;
200
+ if (!t || !t.closest) return;
201
+ const card = t.closest('[data-level-choice]');
202
+ if (card) { choose(card.dataset.levelChoice); return; }
203
+ if (t.closest('#mode-done') || t === modal) { close(); return; }
204
+ const op = t.closest('[data-mode-open], [data-mode-set]');
205
+ if (!op) return;
206
+ // A banner's "Switch to …" sets the mode directly; everything else opens the dialog.
207
+ if (op.dataset.modeSet) { choose(op.dataset.modeSet); return; }
208
+ open(op);
209
+ });
210
+ d.addEventListener('keydown', (e) => {
211
+ if (!modal || modal.classList.contains('hidden')) return;
212
+ if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); return; }
213
+ const step = (e.key === 'ArrowDown' || e.key === 'ArrowRight') ? 1 : (e.key === 'ArrowUp' || e.key === 'ArrowLeft') ? -1 : 0;
214
+ if (step && e.target.closest && e.target.closest('#mode-cards')) {
215
+ e.preventDefault();
216
+ const i = UI_LEVELS.indexOf(currentLevel(d));
217
+ choose(UI_LEVELS[(i + step + UI_LEVELS.length) % UI_LEVELS.length]);
218
+ return;
219
+ }
220
+ if (e.key === 'Tab') { // two stops: the radiogroup and Done
221
+ const stops = [cards.querySelector('[aria-checked="true"]'), $('#mode-done')].filter(Boolean);
222
+ if (!stops.length) return;
223
+ e.preventDefault();
224
+ const at = stops.indexOf(d.activeElement);
225
+ stops[(at + (e.shiftKey ? -1 : 1) + stops.length) % stops.length].focus();
226
+ }
227
+ }, true);
228
+
229
+ return {
230
+ paint,
231
+ open,
232
+ close,
233
+ choose,
234
+ /** A server-confirmed value from elsewhere (GET /api/settings, settings-changed). */
235
+ confirm(level) { if (isUiLevel(level)) { confirmed = level; paint(level); } },
236
+ };
237
+ }