@worca/app 1.2.0 → 1.3.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.
- package/README.md +42 -0
- package/agents/memoryDefragmenter.meta.json +24 -0
- package/agents/worca-cc-code-reviewer.md +6 -1
- package/agents/worca-cc-implementer.md +6 -1
- package/agents/worca-cc-memory-defragmenter.md +32 -0
- package/agents/worca-cc-planner.md +5 -1
- package/package.json +5 -2
- package/src/cli/render.mjs +36 -0
- package/src/cli/worca-cc.mjs +137 -8
- package/src/core/agent-registry.mjs +12 -34
- package/src/core/artifacts.mjs +132 -8
- package/src/core/ask/catalog.mjs +32 -7
- package/src/core/ask/comment-deps.mjs +5 -2
- package/src/core/ask/events.mjs +65 -2
- package/src/core/ask/limits.mjs +9 -0
- package/src/core/ask/mcp-stdio.mjs +10 -0
- package/src/core/ask/memory-deps.mjs +107 -0
- package/src/core/ask/metrics-deps.mjs +124 -0
- package/src/core/ask/metrics-proposal.mjs +175 -0
- package/src/core/ask/prompt.mjs +53 -10
- package/src/core/ask/proposal.mjs +49 -2
- package/src/core/ask/spawn.mjs +21 -4
- package/src/core/ask/store.mjs +14 -5
- package/src/core/ask/tool-deps.mjs +26 -2
- package/src/core/ask/tools.mjs +439 -6
- package/src/core/ask/turn.mjs +163 -4
- package/src/core/ask/workflow-deps.mjs +226 -0
- package/src/core/auto/classify.mjs +352 -0
- package/src/core/auto/fingerprint.mjs +141 -0
- package/src/core/auto/match.mjs +30 -0
- package/src/core/auto/model.mjs +23 -0
- package/src/core/auto/proposal.mjs +132 -0
- package/src/core/auto/recipes.mjs +75 -0
- package/src/core/auto/repo-look.mjs +46 -0
- package/src/core/claude-runner.mjs +132 -11
- package/src/core/config.mjs +120 -3
- package/src/core/db.mjs +44 -1
- package/src/core/diff-comments.mjs +55 -9
- package/src/core/frontmatter.mjs +75 -0
- package/src/core/git-info.mjs +233 -26
- package/src/core/graph/builtin-workflows.mjs +50 -0
- package/src/core/graph/executor.mjs +11 -3
- package/src/core/index-html.mjs +17 -0
- package/src/core/memory-store.mjs +441 -0
- package/src/core/memory-sync.mjs +300 -0
- package/src/core/metrics/ledger.mjs +47 -0
- package/src/core/metrics/lock.mjs +117 -0
- package/src/core/metrics/read.mjs +303 -0
- package/src/core/metrics/record.mjs +389 -0
- package/src/core/metrics/sync.mjs +1100 -0
- package/src/core/onboarding.mjs +99 -0
- package/src/core/orchestrator.mjs +394 -7
- package/src/core/phases.mjs +16 -3
- package/src/core/pipeline-delete.mjs +1 -1
- package/src/core/plugin-store.mjs +2 -10
- package/src/core/preflight.mjs +2 -3
- package/src/core/projects.mjs +16 -1
- package/src/core/run-harness.mjs +458 -32
- package/src/core/run-report.mjs +896 -0
- package/src/core/settings.mjs +162 -0
- package/src/core/sources.mjs +4 -1
- package/src/core/store.mjs +5 -0
- package/src/core/workflow-export.mjs +2 -0
- package/src/core/workflow-share.mjs +1 -0
- package/src/core/workflows.mjs +43 -23
- package/src/core/workspaces.mjs +37 -8
- package/src/shared/graph/agent-meta.mjs +5 -2
- package/src/shared/graph/assemble.mjs +455 -0
- package/src/shared/graph/flow-layout.mjs +249 -0
- package/src/shared/graph/geometry.mjs +48 -28
- package/src/shared/graph/isomorphic.mjs +101 -0
- package/src/shared/report-reasons.mjs +58 -0
- package/src/shared/team-metrics/aggregate.mjs +341 -0
- package/src/shared/team-metrics/workspace-match.mjs +13 -0
- package/ui/public/about-links.mjs +21 -0
- package/ui/public/app.js +3715 -479
- package/ui/public/artifact-view.mjs +135 -0
- package/ui/public/ask-model.mjs +18 -1
- package/ui/public/ask-panel.mjs +1359 -214
- package/ui/public/ask-run-card.mjs +209 -0
- package/ui/public/assets/worca-logo-mask.png +0 -0
- package/ui/public/assets/worca-mark-mask.png +0 -0
- package/ui/public/auto-build.mjs +95 -0
- package/ui/public/auto-proposal.mjs +174 -0
- package/ui/public/comment-thread.mjs +55 -0
- package/ui/public/getting-started.mjs +261 -0
- package/ui/public/graph/composer.mjs +41 -5
- package/ui/public/graph/inspector.mjs +3 -1
- package/ui/public/graph/model.mjs +1 -0
- package/ui/public/graph/run-hosts.mjs +73 -12
- package/ui/public/graph/view.mjs +218 -50
- package/ui/public/guide-spot.mjs +215 -0
- package/ui/public/index.html +423 -25
- package/ui/public/memory-view.mjs +192 -0
- package/ui/public/node-tunables.mjs +201 -0
- package/ui/public/report-run.mjs +75 -0
- package/ui/public/results-view.mjs +25 -0
- package/ui/public/source-pane.mjs +16 -2
- package/ui/public/stats-view.mjs +2 -2
- package/ui/public/style.css +1450 -303
- package/ui/public/team-metrics-surfaces.mjs +452 -0
- package/ui/public/team-metrics-view.mjs +533 -0
- package/ui/public/thinking-orb.mjs +46 -8
- package/ui/server.mjs +1282 -193
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// ui/public/artifact-view.mjs — typed viewers for per-step run artifacts.
|
|
2
|
+
//
|
|
3
|
+
// Markdown reuses ask-markdown.mjs's createMarkdownRenderer verbatim — the SAME
|
|
4
|
+
// sanitizer the Ask panel uses (marked + DOMPurify, one shared allowlist +
|
|
5
|
+
// post-pass), so there is exactly ONE security-sensitive markdown path to audit.
|
|
6
|
+
// `escapeHtml`, `viewerKindFor` and `artifactsByNodeCycle` are pure and
|
|
7
|
+
// node-testable; the renderers touch the DOM and (for markdown) lazily load the
|
|
8
|
+
// vendor bundle through an injected `deps.loadMarkdown`, so a test harness can
|
|
9
|
+
// stub it the way window.__worcaTestHooks?.askMarkdown does.
|
|
10
|
+
import { createMarkdownRenderer } from './ask-markdown.mjs';
|
|
11
|
+
|
|
12
|
+
/** Escape the five HTML metacharacters. Pure. */
|
|
13
|
+
export function escapeHtml(s) {
|
|
14
|
+
return String(s ?? '')
|
|
15
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
16
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pick a viewer for an artifact by kind + relPath. Pure.
|
|
21
|
+
* @param {string} kind
|
|
22
|
+
* @param {string} [relPath]
|
|
23
|
+
* @returns {'markdown'|'diff'|'json'|'text'}
|
|
24
|
+
*/
|
|
25
|
+
export function viewerKindFor(kind, relPath = '') {
|
|
26
|
+
const ext = (String(relPath).split('.').pop() || '').toLowerCase();
|
|
27
|
+
// An explicit file extension is authoritative; the generic kind (plan/review/
|
|
28
|
+
// result) is only a fallback for extensionless paths, so a 'result' that is a
|
|
29
|
+
// .json renders as JSON rather than being forced through the diff viewer.
|
|
30
|
+
if (ext === 'md') return 'markdown';
|
|
31
|
+
if (ext === 'diff' || ext === 'patch') return 'diff';
|
|
32
|
+
if (ext === 'json') return 'json';
|
|
33
|
+
if (kind === 'plan' || kind === 'review') return 'markdown';
|
|
34
|
+
if (kind === 'result') return 'diff';
|
|
35
|
+
return 'text';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Group a run's artifacts by nodeId then cycle for the per-node UI. Legacy
|
|
40
|
+
* artifacts (nodeId == null) fall into the '__run__' bucket. Pure.
|
|
41
|
+
* @param {Array<{nodeId?:string|null, cycle?:number|null}>} [artifacts]
|
|
42
|
+
* @returns {Map<string, Map<number, Array>>} nodeId -> cycle -> artifacts[]
|
|
43
|
+
*/
|
|
44
|
+
export function artifactsByNodeCycle(artifacts = []) {
|
|
45
|
+
const groups = new Map(); // nodeId -> Map(cycle -> [])
|
|
46
|
+
for (const a of artifacts) {
|
|
47
|
+
const node = a.nodeId ?? '__run__';
|
|
48
|
+
const cyc = a.cycle ?? 0;
|
|
49
|
+
if (!groups.has(node)) groups.set(node, new Map());
|
|
50
|
+
const byCyc = groups.get(node);
|
|
51
|
+
if (!byCyc.has(cyc)) byCyc.set(cyc, []);
|
|
52
|
+
byCyc.get(cyc).push(a);
|
|
53
|
+
}
|
|
54
|
+
return groups;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Render escaped plaintext into a <pre>. */
|
|
58
|
+
export function renderText(text, mount) {
|
|
59
|
+
const pre = mount.ownerDocument.createElement('pre');
|
|
60
|
+
pre.className = 'artifact-text';
|
|
61
|
+
pre.textContent = String(text ?? '');
|
|
62
|
+
mount.replaceChildren(pre);
|
|
63
|
+
return pre;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Pretty-print JSON (falls back to plaintext when it does not parse). */
|
|
67
|
+
export function renderJson(text, mount) {
|
|
68
|
+
let pretty = String(text ?? '');
|
|
69
|
+
try { pretty = JSON.stringify(JSON.parse(pretty), null, 2); } catch { /* keep raw text */ }
|
|
70
|
+
const pre = mount.ownerDocument.createElement('pre');
|
|
71
|
+
pre.className = 'artifact-json';
|
|
72
|
+
pre.textContent = pretty;
|
|
73
|
+
mount.replaceChildren(pre);
|
|
74
|
+
return pre;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Render a unified diff, colouring +/-/@@ lines. */
|
|
78
|
+
export function renderDiff(text, mount) {
|
|
79
|
+
const doc = mount.ownerDocument;
|
|
80
|
+
const pre = doc.createElement('pre');
|
|
81
|
+
pre.className = 'artifact-diff';
|
|
82
|
+
for (const line of String(text ?? '').split('\n')) {
|
|
83
|
+
const row = doc.createElement('span');
|
|
84
|
+
// File headers are the space-terminated '+++ '/'--- ' forms (plus diff/index);
|
|
85
|
+
// a bare '+++'/'---' with no space is real added/removed CONTENT (e.g. deleting
|
|
86
|
+
// '--flag' yields the line '---flag'), so classify meta FIRST, then +/-.
|
|
87
|
+
row.className = 'artifact-diff-line'
|
|
88
|
+
+ (line.startsWith('+++ ') || line.startsWith('--- ')
|
|
89
|
+
|| line.startsWith('diff ') || line.startsWith('index ') ? ' meta'
|
|
90
|
+
: line.startsWith('+') ? ' add'
|
|
91
|
+
: line.startsWith('-') ? ' del'
|
|
92
|
+
: line.startsWith('@@') ? ' hunk' : '');
|
|
93
|
+
row.textContent = `${line}\n`;
|
|
94
|
+
pre.appendChild(row);
|
|
95
|
+
}
|
|
96
|
+
mount.replaceChildren(pre);
|
|
97
|
+
return pre;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Render markdown through ask-markdown.mjs's createMarkdownRenderer — the SAME
|
|
102
|
+
* marked + DOMPurify sanitizer, allowlist and post-pass the Ask panel uses, so
|
|
103
|
+
* untrusted artifact content has no separate security path. `deps.loadMarkdown`
|
|
104
|
+
* returns { marked, createDOMPurify } (the seam app.js wires to
|
|
105
|
+
* window.__worcaTestHooks?.askMarkdown). Falls back to escaped plaintext when the
|
|
106
|
+
* bundle is unavailable or parsing/sanitizing fails.
|
|
107
|
+
*/
|
|
108
|
+
export async function renderMarkdown(text, mount, deps = {}) {
|
|
109
|
+
const doc = mount.ownerDocument;
|
|
110
|
+
const load = deps.loadMarkdown;
|
|
111
|
+
if (typeof load !== 'function') return renderText(text, mount);
|
|
112
|
+
const renderer = createMarkdownRenderer({ doc, load });
|
|
113
|
+
if (!(await renderer.ensure())) return renderText(text, mount);
|
|
114
|
+
const out = renderer.render(text);
|
|
115
|
+
if (out.kind !== 'md') return renderText(text, mount);
|
|
116
|
+
const box = doc.createElement('div');
|
|
117
|
+
box.className = 'artifact-markdown';
|
|
118
|
+
box.appendChild(out.frag);
|
|
119
|
+
mount.replaceChildren(box);
|
|
120
|
+
return box;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Render one artifact into `mount`, dispatching on viewerKindFor.
|
|
125
|
+
* @param {{kind:string, relPath:string, text:string}} artifact
|
|
126
|
+
* @param {Element} mount
|
|
127
|
+
* @param {{loadMarkdown?:Function}} [deps]
|
|
128
|
+
*/
|
|
129
|
+
export async function renderArtifact({ kind, relPath, text }, mount, deps = {}) {
|
|
130
|
+
const view = viewerKindFor(kind, relPath);
|
|
131
|
+
if (view === 'markdown') return renderMarkdown(text, mount, deps);
|
|
132
|
+
if (view === 'diff') return renderDiff(text, mount);
|
|
133
|
+
if (view === 'json') return renderJson(text, mount);
|
|
134
|
+
return renderText(text, mount);
|
|
135
|
+
}
|
package/ui/public/ask-model.mjs
CHANGED
|
@@ -23,7 +23,7 @@ export function createThreadModel({ threadId }) {
|
|
|
23
23
|
let worktrees = []; // P4 §10: the chat's open worktrees (snapshot + ask-worktrees frames); the panel mirrors it
|
|
24
24
|
|
|
25
25
|
function newDirty() {
|
|
26
|
-
// runLinks dirt
|
|
26
|
+
// runLinks dirt: the panel's progress cards re-resolve their run identity on it (ask-panel.mjs flushExtra).
|
|
27
27
|
return { structure: false, messages: new Set(), blocks: new Map(), answer: new Set(), label: false, meters: false, title: false, runLinks: false, worktrees: false };
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -285,12 +285,29 @@ export function createThreadModel({ threadId }) {
|
|
|
285
285
|
inFlight() { return inFlight; },
|
|
286
286
|
live() { return live; },
|
|
287
287
|
runLinks() { return links; },
|
|
288
|
+
/** The link for a History pipeline id, with its runId (the Map key) folded in. The server keeps ONE row per
|
|
289
|
+
* (thread, pipeline): POST /api/run inserts once, resumeRun MOVES the row to the new runId, track_run dedupes —
|
|
290
|
+
* so "last hit wins" only ever matters for the live frame path, where the newer frame lands later. */
|
|
291
|
+
runLinkByPipeline(pipelineId) {
|
|
292
|
+
if (!pipelineId) return null;
|
|
293
|
+
let hit = null;
|
|
294
|
+
for (const [runId, l] of links) if (l.pipelineId === pipelineId) hit = { runId, ...l };
|
|
295
|
+
return hit;
|
|
296
|
+
},
|
|
297
|
+
/** The link a run-proposal card launched (ask_run_links.card_id) — stable across a resume. */
|
|
298
|
+
runLinkForCard(cardId) {
|
|
299
|
+
if (!cardId) return null;
|
|
300
|
+
let hit = null;
|
|
301
|
+
for (const [runId, l] of links) if (l.cardId === cardId) hit = { runId, ...l };
|
|
302
|
+
return hit;
|
|
303
|
+
},
|
|
288
304
|
worktrees() { return worktrees; },
|
|
289
305
|
setWorktrees(list) { // the panel's heal (refreshWorktrees) feeds the same store the frames do
|
|
290
306
|
worktrees = Array.isArray(list) ? list.slice() : [];
|
|
291
307
|
dirty.worktrees = true;
|
|
292
308
|
},
|
|
293
309
|
attachmentsBytes() { return attachments.reduce((n, a) => n + (a && Number.isFinite(a.bytes) ? a.bytes : 0), 0); },
|
|
310
|
+
attachments() { return attachments.map((a) => ({ ...a })); }, // the run card's @-popover + pills
|
|
294
311
|
findCard(cardId) {
|
|
295
312
|
for (const r of rows) {
|
|
296
313
|
const b = (r && Array.isArray(r.blocks) ? r.blocks : []).find((x) => x && x.kind === 'card' && x.id === cardId);
|