@worca/app 1.3.0 → 1.4.0-rc.1

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
@@ -2,8 +2,11 @@
2
2
  // The guided click (docs/getting-started.md): scrim-dim the page, keep ONE real
3
3
  // control lit and interactive above it, ring it, say why in one balloon.
4
4
  // Action-driven, never narrated — the target's real click is what advances or
5
- // ends a guide (the caller's onTargetClick); the scrim, Esc and Skip dismiss
6
- // (onDismiss). Soft-block: nothing here can trap the user.
5
+ // ends a guide (the caller's onTargetClick); Esc and Skip dismiss (onDismiss); a
6
+ // click on the scrim only nudges (one pulse of ring + balloon), so a stray click
7
+ // never ends a guide. A hop that only explains (the canvas, a panel) or whose state is
8
+ // already there gets a Next button (onNext) so the user can move on without
9
+ // touching anything. Soft-block: nothing here can trap the user.
7
10
  //
8
11
  // Layering (style.css .guide-*): `spotlight` sits above the Ask dock (40) and
9
12
  // below every .viewer-modal (50) — a dialog that opens on the target's click
@@ -16,6 +19,8 @@
16
19
 
17
20
  const ATTACH_TRIES = 120; // ~2 s at 60 fps: a view's list may still be fetching
18
21
  const REACQUIRE_FRAMES = 60; // ~1 s: how long a vanished control may take to be repainted
22
+ const GLIDE_MS = 2500; // how long the guide's own smooth scroll may still be emitting scroll events
23
+ const BRING_EVERY = 1200; // ms between two re-scrolls of a control the page carried out of view
19
24
 
20
25
  const frame = (win) => (typeof win.requestAnimationFrame === 'function'
21
26
  ? (fn) => win.requestAnimationFrame(fn)
@@ -47,13 +52,15 @@ function visible(el) {
47
52
  * @param {number} [o.tries] attach retries before giving up (→ onDismiss)
48
53
  * @param {() => void} o.onDismiss
49
54
  * @param {() => void} o.onTargetClick
55
+ * @param {() => void} [o.onNext] when given, a Next button sits beside Skip and calls it
56
+ * @param {string} [o.nextLabel] its label (default "Next")
50
57
  * @param {Document} [o.doc]
51
58
  * @param {Window} [o.win]
52
59
  * @returns {{destroy:() => void, layer:Element}}
53
60
  */
54
61
  export function createGuideSpot({
55
62
  target, text, mode = 'spotlight', lift = [], tries = ATTACH_TRIES,
56
- onDismiss, onTargetClick, doc = globalThis.document, win = globalThis.window,
63
+ onDismiss, onTargetClick, onNext = null, nextLabel = 'Next', doc = globalThis.document, win = globalThis.window,
57
64
  }) {
58
65
  const targets = Array.isArray(target) ? target : [target];
59
66
  const texts = Array.isArray(text) ? text : [text];
@@ -70,7 +77,10 @@ export function createGuideSpot({
70
77
  if (mode === 'spotlight') {
71
78
  scrim = doc.createElement('div');
72
79
  scrim.className = 'guide-scrim';
73
- scrim.addEventListener('click', () => dismiss());
80
+ // A click beside the lit control is swallowed, never an exit: a stray click used to end
81
+ // the guide silently, which reads as a bug. The ring and balloon pulse once to say
82
+ // "here" instead. Skip and Esc remain the exits.
83
+ scrim.addEventListener('click', () => nudge());
74
84
  layer.appendChild(scrim);
75
85
  }
76
86
  const ring = doc.createElement('div');
@@ -88,12 +98,34 @@ export function createGuideSpot({
88
98
  skip.className = 'guide-skip';
89
99
  skip.textContent = 'Skip';
90
100
  skip.addEventListener('click', () => dismiss());
91
- balloon.append(line, skip);
101
+ const actions = doc.createElement('div');
102
+ actions.className = 'guide-actions';
103
+ if (typeof onNext === 'function') {
104
+ const next = doc.createElement('button');
105
+ next.type = 'button';
106
+ next.className = 'guide-next';
107
+ next.textContent = nextLabel || 'Next';
108
+ // The caller re-derives the hop and replaces this layer; nothing is dismissed here.
109
+ next.addEventListener('click', () => { if (alive) onNext(); });
110
+ actions.appendChild(next);
111
+ }
112
+ actions.appendChild(skip);
113
+ balloon.append(line, actions);
92
114
  layer.append(ring, balloon);
93
115
  doc.body.appendChild(layer);
94
116
 
95
117
  const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); dismiss(); } };
96
118
  doc.addEventListener('keydown', onKey);
119
+ // The user's own scrolling (wheel, touch, paging keys, a scrollbar drag — any scroll that is not
120
+ // the tail of the guide's own glide) — after it, the guide never re-scrolls.
121
+ let userScrolled = false;
122
+ let lastBring = 0;
123
+ const onUserScroll = (e) => {
124
+ if (e.type === 'keydown' && !['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Home', 'End', ' '].includes(e.key)) return;
125
+ if (e.type === 'scroll' && Date.now() - lastBring < GLIDE_MS) return;
126
+ userScrolled = true;
127
+ };
128
+ for (const t of ['wheel', 'touchmove', 'keydown', 'scroll']) doc.addEventListener(t, onUserScroll, { passive: true, capture: true });
97
129
 
98
130
  function destroy() {
99
131
  if (!alive) return;
@@ -101,6 +133,7 @@ export function createGuideSpot({
101
133
  caf(handle);
102
134
  cleanup();
103
135
  doc.removeEventListener('keydown', onKey);
136
+ for (const t of ['wheel', 'touchmove', 'keydown', 'scroll']) doc.removeEventListener(t, onUserScroll, { capture: true });
104
137
  layer.remove();
105
138
  }
106
139
  function dismiss() {
@@ -108,6 +141,18 @@ export function createGuideSpot({
108
141
  destroy();
109
142
  if (onDismiss) onDismiss();
110
143
  }
144
+ /** Draw the eye to the lit control: one pulse of the ring and the balloon (restarted on repeat). */
145
+ function nudge() {
146
+ if (!alive) return;
147
+ for (const el of [ring, balloon]) {
148
+ el.classList.remove('nudge');
149
+ void el.offsetWidth; // restart the animation when clicked again mid-pulse
150
+ el.classList.add('nudge');
151
+ }
152
+ }
153
+ const onNudgeEnd = (e) => { e.currentTarget.classList.remove('nudge'); };
154
+ ring.addEventListener('animationend', onNudgeEnd);
155
+ balloon.addEventListener('animationend', onNudgeEnd);
111
156
 
112
157
  /** Centre the balloon under its target, clamped to the viewport, arrow aimed
113
158
  * at the target's centre WITHIN the balloon; above the target when there is
@@ -126,10 +171,24 @@ export function createGuideSpot({
126
171
  const centre = rect.left + rect.width / 2;
127
172
  const left = Math.max(8, Math.min(centre - box.width / 2, Math.max(8, vw - box.width - 8)));
128
173
  const gap = 14;
129
- const above = vh > 0 && rect.bottom + gap + box.height > vh - 8 && rect.top - gap - box.height >= 8;
174
+ // "Fits" means inside the viewport, not merely on that side of the control: a control
175
+ // scrolled out of view has no room on either side.
176
+ const fitsBelow = vh > 0 && rect.bottom + gap >= 8 && rect.bottom + gap + box.height <= vh - 8;
177
+ const fitsAbove = rect.top - gap - box.height >= 8 && rect.top - gap <= vh - 8;
178
+ // Neither edge has room (a run card taller than the window): the balloon is PINNED inside
179
+ // the viewport, over the target, arrow off — never parked off-screen where "the guide is
180
+ // stuck" is all the user can see.
181
+ const pinned = vh > 0 && !fitsBelow && !fitsAbove;
182
+ const above = !pinned && !fitsBelow && fitsAbove;
130
183
  balloon.classList.toggle('above', above);
184
+ balloon.classList.toggle('pinned', pinned);
131
185
  balloon.style.left = `${left}px`;
132
- balloon.style.top = above ? `${rect.top - box.height - gap}px` : `${rect.bottom + gap}px`;
186
+ if (pinned) {
187
+ const top = Math.max(16, Math.min(rect.top + gap, vh - box.height - 16));
188
+ balloon.style.top = `${top}px`;
189
+ } else {
190
+ balloon.style.top = above ? `${rect.top - box.height - gap}px` : `${rect.bottom + gap}px`;
191
+ }
133
192
  balloon.style.setProperty('--arrow-x', `${Math.max(16, Math.min(centre - left, box.width - 16))}px`);
134
193
  }
135
194
 
@@ -149,6 +208,13 @@ export function createGuideSpot({
149
208
  return;
150
209
  }
151
210
  el.classList.add('guide-target');
211
+ // The elevation needs a positioned box. A static control gets position:relative;
212
+ // one that is already positioned (the Composer's floating rail is absolute) keeps
213
+ // its own position — forcing relative would pull it out of place.
214
+ try {
215
+ const pos = win.getComputedStyle(el).position;
216
+ if (!pos || pos === 'static') el.classList.add('guide-target-static');
217
+ } catch { el.classList.add('guide-target-static'); }
152
218
  line.textContent = texts[which] ?? texts[0] ?? '';
153
219
  layer.dataset.target = targets[which];
154
220
  // The ring follows the control's own corner radius (a pill stays a pill).
@@ -168,10 +234,17 @@ export function createGuideSpot({
168
234
  }
169
235
  // Glide to the control (the ring follows it per frame, so the eye is led
170
236
  // down the page); reduced motion jumps.
237
+ let bring = () => {};
171
238
  if (typeof el.scrollIntoView === 'function') {
172
239
  let reduced = false;
173
240
  try { reduced = !!(win.matchMedia && win.matchMedia('(prefers-reduced-motion: reduce)').matches); } catch { /* jsdom */ }
174
- try { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: reduced ? 'auto' : 'smooth' }); } catch { /* jsdom */ }
241
+ // A control taller than most of the window is brought to its top, not its middle (which
242
+ // would leave its head — and the balloon — above the fold).
243
+ let block = 'center';
244
+ try { const vh = win.innerHeight || 0; if (vh && el.getBoundingClientRect().height > vh * 0.6) block = 'start'; } catch { /* jsdom */ }
245
+ bring = () => { lastBring = Date.now(); try { el.scrollIntoView({ block, inline: 'nearest', behavior: reduced ? 'auto' : 'smooth' }); } catch { /* jsdom */ } };
246
+ userScrolled = false;
247
+ bring();
175
248
  }
176
249
  const onClick = () => {
177
250
  if (!alive) return;
@@ -198,12 +271,17 @@ export function createGuideSpot({
198
271
  return;
199
272
  }
200
273
  const b = el.getBoundingClientRect();
274
+ // A view that keeps settling (a run list re-sorting its cards, a log growing) can carry
275
+ // the control ENTIRELY out of view after the glide: bring it back — throttled, so a glide
276
+ // in flight is not restarted every frame — but never over the user's own scrolling.
277
+ const vh = win.innerHeight || 0;
278
+ if (!userScrolled && vh && (b.bottom < 0 || b.top > vh) && Date.now() - lastBring > BRING_EVERY) bring();
201
279
  const key = `${b.top}|${b.left}|${b.width}|${b.height}`;
202
280
  if (key !== last) { last = key; place(b); }
203
281
  handle = raf(track);
204
282
  };
205
283
  cleanup = () => {
206
- el.classList.remove('guide-target');
284
+ el.classList.remove('guide-target', 'guide-target-static');
207
285
  for (const a of lifted) a.classList.remove('guide-lift', 'guide-lift-static');
208
286
  el.removeEventListener('click', onClick);
209
287
  };