@worca/app 1.0.0 → 1.1.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.
- package/README.md +22 -9
- package/agents/clarify.meta.json +4 -4
- package/agents/decomposer.meta.json +5 -5
- package/agents/implementer.meta.json +15 -5
- package/agents/manualTestsChecklist.meta.json +5 -4
- package/agents/manualWebUiTesting.meta.json +9 -4
- package/agents/planReviewer.meta.json +12 -4
- package/agents/planner.meta.json +12 -5
- package/agents/refiner.meta.json +15 -4
- package/agents/reviewer.meta.json +14 -4
- package/agents/worca-cc-clarify.md +7 -0
- package/agents/worca-cc-code-reviewer.md +11 -6
- package/agents/worca-cc-decomposer.md +7 -0
- package/agents/worca-cc-implementer.md +9 -0
- package/agents/worca-cc-manual-tests-checklist.md +8 -5
- package/agents/worca-cc-manual-web-ui-testing.md +10 -6
- package/agents/worca-cc-plan-refiner.md +11 -6
- package/agents/worca-cc-plan-reviewer.md +10 -7
- package/agents/worca-cc-planner.md +9 -0
- package/agents/worca-cc-workspace-reviewer.md +11 -4
- package/agents/worca-cc-workspace-scanner.md +8 -4
- package/agents/workspaceReviewer.meta.json +15 -4
- package/agents/workspaceScanner.meta.json +5 -4
- package/package.json +8 -2
- package/skills/worca/SKILL.md +5 -5
- package/src/cli/render.mjs +148 -0
- package/src/cli/worca-cc.mjs +319 -45
- package/src/core/agent-gen.mjs +69 -31
- package/src/core/agent-registry.mjs +124 -144
- package/src/core/agent-store.mjs +164 -4
- package/src/core/artifacts.mjs +189 -21
- package/src/core/ask/catalog.mjs +111 -0
- package/src/core/ask/comment-deps.mjs +55 -0
- package/src/core/ask/events.mjs +506 -0
- package/src/core/ask/follow.mjs +107 -0
- package/src/core/ask/git-allowlist.mjs +226 -0
- package/src/core/ask/limits.mjs +54 -0
- package/src/core/ask/mcp-stdio.mjs +135 -0
- package/src/core/ask/models.mjs +125 -0
- package/src/core/ask/prompt.mjs +261 -0
- package/src/core/ask/proposal.mjs +170 -0
- package/src/core/ask/redact.mjs +30 -0
- package/src/core/ask/spawn.mjs +153 -0
- package/src/core/ask/store.mjs +360 -0
- package/src/core/ask/tool-deps.mjs +63 -0
- package/src/core/ask/tools.mjs +848 -0
- package/src/core/ask/turn.mjs +416 -0
- package/src/core/ask/worktree-deps.mjs +27 -0
- package/src/core/ask/worktrees.mjs +285 -0
- package/src/core/chat/command-router.mjs +20 -3
- package/src/core/claude-runner.mjs +434 -57
- package/src/core/config.mjs +264 -41
- package/src/core/cost-budget.mjs +29 -2
- package/src/core/db.mjs +684 -47
- package/src/core/diff-anchor.mjs +213 -0
- package/src/core/diff-comments.mjs +273 -0
- package/src/core/engine-select.mjs +32 -0
- package/src/core/git-info.mjs +49 -10
- package/src/core/graph/builtin-workflows.mjs +51 -0
- package/src/core/graph/executor.mjs +894 -0
- package/src/core/graph/registry-ports.mjs +12 -0
- package/src/core/graph/scheduler.mjs +1065 -0
- package/src/core/graph/seed-templates.mjs +318 -0
- package/src/core/model-env.mjs +112 -8
- package/src/core/model-test.mjs +79 -0
- package/src/core/orchestrator.mjs +902 -4098
- package/src/core/overview-agent.mjs +15 -3
- package/src/core/phases.mjs +208 -537
- package/src/core/pipeline-delete.mjs +13 -2
- package/src/core/plugin-api.mjs +8 -3
- package/src/core/plugin-config.mjs +178 -28
- package/src/core/plugin-inventory.mjs +6 -2
- package/src/core/plugin-manifest.mjs +199 -11
- package/src/core/plugin-models.mjs +1 -0
- package/src/core/plugin-repo.mjs +16 -4
- package/src/core/plugin-shim-child.mjs +9 -3
- package/src/core/plugin-shim.mjs +77 -14
- package/src/core/plugin-store.mjs +236 -29
- package/src/core/plugin-workflows.mjs +90 -41
- package/src/core/preflight.mjs +135 -3
- package/src/core/projects.mjs +7 -5
- package/src/core/protocol.mjs +8 -35
- package/src/core/recoverable-error.mjs +1 -1
- package/src/core/run-harness.mjs +3585 -0
- package/src/core/run-manifest.mjs +5 -1
- package/src/core/settings.mjs +109 -13
- package/src/core/skills.mjs +10 -3
- package/src/core/source-bindings.mjs +175 -0
- package/src/core/sources.mjs +87 -25
- package/src/core/stats.mjs +25 -6
- package/src/core/title.mjs +51 -4
- package/src/core/workflows.mjs +358 -259
- package/src/core/workspace-scan.mjs +4 -0
- package/src/core/worktree.mjs +98 -7
- package/src/shared/graph/agent-meta.mjs +278 -0
- package/src/shared/graph/constants.mjs +105 -0
- package/src/shared/graph/geometry.mjs +157 -0
- package/src/shared/graph/layout.mjs +134 -0
- package/src/shared/graph/loops.mjs +130 -0
- package/src/shared/graph/manifest.mjs +257 -0
- package/src/shared/graph/ports.mjs +153 -0
- package/src/shared/graph/route.mjs +397 -0
- package/src/shared/graph/template.mjs +165 -0
- package/src/shared/graph/thumbnail.mjs +67 -0
- package/src/shared/graph/validate.mjs +491 -0
- package/src/shared/graph/verdict.mjs +41 -0
- package/ui/public/app.js +4008 -1670
- package/ui/public/ask-markdown.mjs +145 -0
- package/ui/public/ask-model.mjs +264 -0
- package/ui/public/ask-panel.mjs +1880 -0
- package/ui/public/chat-settings-view.mjs +6 -2
- package/ui/public/diff-view.mjs +66 -11
- package/ui/public/file-tree.mjs +305 -0
- package/ui/public/graph/composer.mjs +889 -0
- package/ui/public/graph/inspector.mjs +183 -0
- package/ui/public/graph/model.mjs +37 -0
- package/ui/public/graph/palette.mjs +144 -0
- package/ui/public/graph/run-decor.mjs +410 -0
- package/ui/public/graph/run-hosts.mjs +201 -0
- package/ui/public/graph/save-dialog.mjs +56 -0
- package/ui/public/graph/view.mjs +858 -0
- package/ui/public/guardrails-view.mjs +4 -2
- package/ui/public/hljs-loader.mjs +180 -0
- package/ui/public/index.html +269 -265
- package/ui/public/log-filter.mjs +22 -4
- package/ui/public/log-line.mjs +45 -19
- package/ui/public/models-view.mjs +171 -9
- package/ui/public/plugins-view.mjs +106 -4
- package/ui/public/source-pane.mjs +190 -8
- package/ui/public/stats-view.mjs +81 -1
- package/ui/public/style.css +1459 -229
- package/ui/public/syntax-highlight.mjs +270 -0
- package/ui/public/thinking-orb.mjs +110 -0
- package/ui/server.mjs +1667 -98
- package/src/core/channels.mjs +0 -302
- package/src/core/runners.mjs +0 -167
- package/src/core/workflow-validator.mjs +0 -185
- package/ui/public/composer-core.mjs +0 -211
package/src/core/stats.mjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// archive exists so these numbers survive history cleanup (spec §6.9). Pure
|
|
4
4
|
// synchronous DB reads; no gh, no git, offline-correct.
|
|
5
5
|
//
|
|
6
|
-
// Attribution: money = cost_ledger by event timestamp
|
|
7
|
-
// and PRs = cohort by started_at (a run belongs to the
|
|
8
|
-
// in, with its CURRENT status). 'today' differs: its totals count runs whose
|
|
6
|
+
// Attribution: money = cost_ledger + ask_cost_ledger by event timestamp
|
|
7
|
+
// (exact); runs, time, and PRs = cohort by started_at (a run belongs to the
|
|
8
|
+
// bucket its start falls in, with its CURRENT status). 'today' differs: its totals count runs whose
|
|
9
9
|
// lifespan [started_at, updated_at] overlaps the day ("active today"), and its
|
|
10
10
|
// hourly bars attribute terminal outcomes to the hour of the terminal write
|
|
11
11
|
// (updated_at). 'all' money/time use the fallback-aware pipelines sums so
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { prepare } from './db.mjs';
|
|
15
15
|
import {
|
|
16
16
|
budgetStatus, costWindowStart, costWindowEnd, allTimeTotals, roundUsd,
|
|
17
|
+
askWindowedSpendUsd,
|
|
17
18
|
} from './cost-budget.mjs';
|
|
18
19
|
|
|
19
20
|
const RANGES = ['today', 'week', 'month', 'all'];
|
|
@@ -84,6 +85,18 @@ function ledgerSpend(fromMs, toMs) {
|
|
|
84
85
|
return roundUsd(row?.s || 0);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/** Ask Worca window aggregate (ask-cost-statistics-design.md D5/D6): spend,
|
|
89
|
+
* DISTINCT sessions, turns. A thread is a session in every window it has a
|
|
90
|
+
* costed turn in. */
|
|
91
|
+
function askTotals(fromMs, toMs) {
|
|
92
|
+
const row = prepare(`
|
|
93
|
+
SELECT COALESCE(SUM(amount_usd), 0) AS s,
|
|
94
|
+
COUNT(DISTINCT thread_id) AS sessions,
|
|
95
|
+
COUNT(*) AS turns
|
|
96
|
+
FROM ask_cost_ledger WHERE ts >= ? AND ts < ?`).get(fromMs, toMs);
|
|
97
|
+
return { spendUsd: roundUsd(row?.s || 0), sessions: row?.sessions || 0, turns: row?.turns || 0 };
|
|
98
|
+
}
|
|
99
|
+
|
|
87
100
|
/** Build zero-filled buckets [{startMs, endMs}] from windowStart through `now`. */
|
|
88
101
|
function buildBuckets(windowStart, now, bucket) {
|
|
89
102
|
const out = [];
|
|
@@ -137,8 +150,11 @@ export function getStats({ range = 'month', now = new Date() } = {}) {
|
|
|
137
150
|
const windowTotalsFn = range === 'today' ? activeTotals : cohortTotals;
|
|
138
151
|
const shapeTotals = (fromMs, toMs) => {
|
|
139
152
|
const c = windowTotalsFn(fromMs, toMs);
|
|
153
|
+
const pipelineSpendUsd = ledgerSpend(fromMs, toMs);
|
|
154
|
+
const ask = askTotals(fromMs, toMs);
|
|
140
155
|
return {
|
|
141
|
-
spentUsd:
|
|
156
|
+
spentUsd: roundUsd(pipelineSpendUsd + ask.spendUsd),
|
|
157
|
+
pipelineSpendUsd, ask,
|
|
142
158
|
workedMs: c.workedMs,
|
|
143
159
|
runs: c.runs, finished: c.finished, stopped: c.stopped, failed: c.failed,
|
|
144
160
|
paused: c.paused, running: c.running,
|
|
@@ -150,8 +166,11 @@ export function getStats({ range = 'month', now = new Date() } = {}) {
|
|
|
150
166
|
if (range === 'all') {
|
|
151
167
|
const at = allTimeTotals();
|
|
152
168
|
const c = cohortTotals(0, windowEnd.getTime());
|
|
169
|
+
const ask = askTotals(0, windowEnd.getTime());
|
|
153
170
|
totals = {
|
|
154
|
-
spentUsd: at.spendUsd
|
|
171
|
+
spentUsd: roundUsd(at.spendUsd + ask.spendUsd),
|
|
172
|
+
pipelineSpendUsd: at.spendUsd, ask,
|
|
173
|
+
workedMs: at.activeMs,
|
|
155
174
|
runs: c.runs, finished: c.finished, stopped: c.stopped, failed: c.failed,
|
|
156
175
|
paused: c.paused, running: c.running,
|
|
157
176
|
prsOpened: c.prsOpened, prsMerged: c.prsMerged,
|
|
@@ -169,7 +188,7 @@ export function getStats({ range = 'month', now = new Date() } = {}) {
|
|
|
169
188
|
const c = bucketTotalsFn(startMs, endMs);
|
|
170
189
|
return {
|
|
171
190
|
bucketStartMs: startMs,
|
|
172
|
-
spentUsd: ledgerSpend(startMs, endMs),
|
|
191
|
+
spentUsd: roundUsd(ledgerSpend(startMs, endMs) + askWindowedSpendUsd(startMs, endMs)),
|
|
173
192
|
finished: c.finished, stopped: c.stopped, failed: c.failed,
|
|
174
193
|
};
|
|
175
194
|
});
|
package/src/core/title.mjs
CHANGED
|
@@ -11,6 +11,8 @@ const SYSTEM = [
|
|
|
11
11
|
'You write a SHORT, human-readable title for a software task.',
|
|
12
12
|
'Rules: 3–8 words, Title Case-ish, no trailing period, no quotes, no markdown,',
|
|
13
13
|
'no preamble. Output ONLY the title on a single line.',
|
|
14
|
+
'Never ask questions, never ask for more context, never apologize or explain.',
|
|
15
|
+
'If the task is vague or conversational, still output your best-guess topic title.',
|
|
14
16
|
].join(' ');
|
|
15
17
|
|
|
16
18
|
/** Normalize raw model output into a safe single-line title (pure, exported for tests). */
|
|
@@ -21,14 +23,45 @@ export function sanitizeTitle(raw) {
|
|
|
21
23
|
t = t.replace(/^(?:title|task)\s*[:\-]\s*/i, ''); // strip "Title:"/"Task:" label
|
|
22
24
|
t = t.replace(/^["'“”`]+|["'“”`]+$/g, '').trim(); // strip wrapping quotes/backticks
|
|
23
25
|
t = t.replace(/\s+/g, ' ').replace(/\.+$/, '').trim(); // collapse ws, drop trailing dots
|
|
24
|
-
|
|
26
|
+
if (t.length > MAX_LEN) {
|
|
27
|
+
// Cut at a word boundary instead of mid-word ("…you'd li"). The window is
|
|
28
|
+
// MAX_LEN+1 so a word ending exactly at the cap survives; a boundary too
|
|
29
|
+
// close to the start (or none at all — one unbroken run) hard-slices.
|
|
30
|
+
const cut = t.slice(0, MAX_LEN + 1);
|
|
31
|
+
const sp = cut.lastIndexOf(' ');
|
|
32
|
+
t = (sp >= 30 ? cut.slice(0, sp) : t.slice(0, MAX_LEN)).replace(/[\s,;:–—-]+$/, '');
|
|
33
|
+
}
|
|
34
|
+
return t.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A title that is really a clarifying question / refusal ("I need more context
|
|
38
|
+
// to write a title…"). First-person or plea openers, a trailing question mark,
|
|
39
|
+
// or prose far beyond the 3–8-word instruction all mean the model did not
|
|
40
|
+
// title the task — the caller should keep its provisional title instead.
|
|
41
|
+
// Narrow on purpose (review of PR #376): bare `what`/`which`/`unable`/`please`
|
|
42
|
+
// openers dropped legitimate titles ("What's New Page Redesign", "Which Tab Is
|
|
43
|
+
// Active Indicator", "Unable To Login Error Fix", "Please Wait Spinner Timing").
|
|
44
|
+
// A question is caught by its trailing `?` above; `unable`/`please` count only
|
|
45
|
+
// with the verbs a refusal actually uses.
|
|
46
|
+
const REFUSAL_OPENER_RE =
|
|
47
|
+
/^(?:i(?:['’](?:m|d|ll|ve))?\s|sorry\b|apolog|unfortunately\b|unable to (?:determine|identify|generate|create|provide|write|title|summari[sz]e|infer|tell|help|assist)\b|please (?:provide|share|clarify|give|specify|tell|include|describe|let me know)\b|could you\b|can you\b|tell me\b|what(?:['’]s)? (?:is|are|was|were|do|does|did|would|should|task|work)\b|which (?:task|one|of)\b|help me\b)/i;
|
|
48
|
+
const MAX_WORDS = 12;
|
|
49
|
+
|
|
50
|
+
/** True when sanitized model output looks like a refusal/question, not a title (pure, exported for tests). */
|
|
51
|
+
export function isRefusalTitle(t) {
|
|
52
|
+
if (!t || typeof t !== 'string') return false;
|
|
53
|
+
if (/\?$/.test(t)) return true;
|
|
54
|
+
if (REFUSAL_OPENER_RE.test(t)) return true;
|
|
55
|
+
return t.split(' ').length > MAX_WORDS;
|
|
25
56
|
}
|
|
26
57
|
|
|
27
58
|
/**
|
|
28
59
|
* Produce a concise LLM title for a prompt. Never throws — returns '' on any
|
|
29
60
|
* failure/abort/empty input so the caller keeps the provisional title.
|
|
61
|
+
* `permissionMode` (default 'acceptEdits') exists for Ask Worca: its title call
|
|
62
|
+
* passes 'dontAsk' so the mock dispatcher can never reach a file-writing role.
|
|
30
63
|
* @param {string} prompt
|
|
31
|
-
* @param {{cwd:string, signal?:AbortSignal, model?:string, bin?:string, envScrub?:boolean, envAllowlist?:string[]}} opts
|
|
64
|
+
* @param {{cwd:string, signal?:AbortSignal, model?:string, bin?:string, mock?:boolean, envScrub?:boolean, envAllowlist?:string[], tools?:string[], strictMcpConfig?:boolean, settingSources?:string[], disableSlashCommands?:boolean, mcpConfigPath?:string, permissionMode?:string}} opts
|
|
32
65
|
* @returns {Promise<string>}
|
|
33
66
|
*/
|
|
34
67
|
export async function generateTitle(prompt, opts = {}) {
|
|
@@ -45,18 +78,32 @@ export async function generateTitle(prompt, opts = {}) {
|
|
|
45
78
|
// env everywhere the id is used.
|
|
46
79
|
modelEnv: resolveModelEnv(opts.model || DEFAULT_TITLE_MODEL),
|
|
47
80
|
effort: 'low',
|
|
48
|
-
permissionMode: 'acceptEdits',
|
|
81
|
+
permissionMode: opts.permissionMode || 'acceptEdits',
|
|
49
82
|
allowedTools: [], // empty → no --allowedTools flag → claude defaults; pure text gen
|
|
50
83
|
signal: opts.signal,
|
|
51
84
|
// Title generation runs DURING a pipeline run, so it must honor the same
|
|
52
85
|
// guardrails as the run itself. All three are undefined when absent, and
|
|
53
86
|
// runClaude treats undefined as "not passed" — existing callers are unchanged.
|
|
54
87
|
bin: opts.bin,
|
|
88
|
+
// A run configured with claude:{mock:true} (tests, `--mock`) must title
|
|
89
|
+
// through runMock too: runClaude decides mock-vs-real from THIS field or
|
|
90
|
+
// WORCA_MOCK, and the 29 test files that only pass the option spawned the
|
|
91
|
+
// developer's real binary 157x per `npm test` (2026-08-30).
|
|
92
|
+
mock: opts.mock,
|
|
55
93
|
envScrub: opts.envScrub,
|
|
56
94
|
envAllowlist: opts.envAllowlist,
|
|
95
|
+
// Ask Worca (ask-worca-design.md §6.8): sandbox hardening pass-through for the
|
|
96
|
+
// chat's background title call. All undefined for every existing caller, and
|
|
97
|
+
// runClaude emits nothing for undefined — pipeline title argv is unchanged.
|
|
98
|
+
tools: opts.tools,
|
|
99
|
+
strictMcpConfig: opts.strictMcpConfig,
|
|
100
|
+
settingSources: opts.settingSources,
|
|
101
|
+
disableSlashCommands: opts.disableSlashCommands,
|
|
102
|
+
mcpConfigPath: opts.mcpConfigPath,
|
|
57
103
|
onEvent: () => {},
|
|
58
104
|
});
|
|
59
|
-
|
|
105
|
+
const title = sanitizeTitle(out);
|
|
106
|
+
return isRefusalTitle(title) ? '' : title;
|
|
60
107
|
} catch (err) {
|
|
61
108
|
if (err && err.name === 'AbortError') return ''; // run was stopped — caller keeps provisional
|
|
62
109
|
return '';
|