@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
package/src/core/ask/spawn.mjs
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// `result` frames); CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 restores the
|
|
11
11
|
// foreground shape. It rides modelEnv: merged last over the scrubbed env,
|
|
12
12
|
// CLAUDE_-prefixed (survives scrub), not a reserved key.
|
|
13
|
+
// - --add-dir <base> + CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 loads <base>/.claude/rules/**
|
|
14
|
+
// (2.1.270, 2026-09-13); without the override the CLI loaded them under `--setting-sources project`
|
|
15
|
+
// alone (V1) but not under the default sources (V2/V4) — keep the documented override.
|
|
13
16
|
// - `--tools <list>` keeps ONLY the named built-ins (Task,Read,Grep,Glob — no
|
|
14
17
|
// Bash/Write/Edit exist); MCP tools survive; `--allowedTools <list>,mcp__worca`
|
|
15
18
|
// under dontAsk runs them without prompting; a deny rule wins over everything.
|
|
@@ -58,6 +61,10 @@ export const ASK_DENY_RULES = Object.freeze([
|
|
|
58
61
|
'Read(~/.config/gh/**)',
|
|
59
62
|
]);
|
|
60
63
|
export const ASK_SPAWN_ENV = Object.freeze({ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: '1' });
|
|
64
|
+
// Native-rules revision: the CLI loads `<dir>/.claude/rules` from an --add-dir only under this
|
|
65
|
+
// override (probes J/J2, 2.1.270; the E2 note in claude-runner.mjs). CLAUDE_-prefixed ⇒ survives
|
|
66
|
+
// the scrub, not a reserved key.
|
|
67
|
+
export const ASK_MEMORY_ENV = Object.freeze({ CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: '1' });
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
70
|
* The per-thread Read allow rules: the chat's worktrees (P4 §6) and its stored
|
|
@@ -82,6 +89,8 @@ export const SANDBOX_NOTE =
|
|
|
82
89
|
'the worca MCP tools (mcp__worca__*). You cannot run commands, edit files or use the network — do not try. ' +
|
|
83
90
|
"The only view into a repository is this chat's read-only detached worktrees: list_worktrees/open_worktree give the path; Read, Grep and Glob work under that path, and the worca `git` tool serves history and diffs. " +
|
|
84
91
|
'The one other place Read may go is the file path read_attachment returns for an image or PDF attachment of this chat; never read anywhere else on disk. ' +
|
|
92
|
+
"Never call propose_workflow or propose_run yourself: proposals belong to the assistant's own turn (a sub-agent's call produces no card). " +
|
|
93
|
+
"Never call remember or forget yourself: saving or removing memory belongs to the assistant's own turn (list_memory and read_memory are fine). " +
|
|
85
94
|
'Answer from tool results only; never invent run data; return a short report.';
|
|
86
95
|
|
|
87
96
|
/** System-prompt-only mock markers (the runner parses the ask role from the SYSTEM prompt, Task 16). */
|
|
@@ -96,9 +105,10 @@ export function buildMockMarkers(card) {
|
|
|
96
105
|
* @param {{maxTurns:number, maxBudgetUsd:number|null}} o.limits from askLimits()
|
|
97
106
|
* @param {string} o.mcpConfigPath the per-turn mcp-<assistantMessageId>.json
|
|
98
107
|
* @param {string} o.scratchDir join(worcaHome(), 'tmp', 'ask') — ONE empty dir for all threads, never the home
|
|
108
|
+
* @param {string|null} [o.memoryDir] refreshAskMemoryMount's base for this turn's scope set; null ⇒ no memory (empty store)
|
|
99
109
|
* @returns {object} runClaude options
|
|
100
110
|
*/
|
|
101
|
-
export function buildAskSpawnOptions({ thread = {}, turn = {}, limits = {}, mcpConfigPath, scratchDir } = {}) {
|
|
111
|
+
export function buildAskSpawnOptions({ thread = {}, turn = {}, limits = {}, mcpConfigPath, scratchDir, memoryDir = null } = {}) {
|
|
102
112
|
if (!scratchDir) throw new Error('buildAskSpawnOptions: scratchDir is required');
|
|
103
113
|
if (!mcpConfigPath) throw new Error('buildAskSpawnOptions: mcpConfigPath is required');
|
|
104
114
|
const systemPrompt = String(turn.systemPrompt ?? '') + (turn.mock ? buildMockMarkers(turn.mock.card) : '');
|
|
@@ -108,7 +118,7 @@ export function buildAskSpawnOptions({ thread = {}, turn = {}, limits = {}, mcpC
|
|
|
108
118
|
systemPrompt,
|
|
109
119
|
model: turn.model,
|
|
110
120
|
effort: turn.effort,
|
|
111
|
-
modelEnv: { ...(turn.modelEnv || {}), ...ASK_SPAWN_ENV },
|
|
121
|
+
modelEnv: { ...(turn.modelEnv || {}), ...ASK_SPAWN_ENV, ...(memoryDir ? ASK_MEMORY_ENV : {}) },
|
|
112
122
|
permissionMode: ASK_PERMISSION_MODE,
|
|
113
123
|
allowedTools: [...ASK_BUILTIN_TOOLS],
|
|
114
124
|
mcpServerGrants: [...ASK_MCP_GRANTS],
|
|
@@ -128,28 +138,35 @@ export function buildAskSpawnOptions({ thread = {}, turn = {}, limits = {}, mcpC
|
|
|
128
138
|
maxTurns: limits.maxTurns,
|
|
129
139
|
maxBudgetUsd: limits.maxBudgetUsd ?? null,
|
|
130
140
|
appendSubagentSystemPrompt: SANDBOX_NOTE,
|
|
141
|
+
addDirs: memoryDir ? [memoryDir] : undefined,
|
|
131
142
|
signal: turn.signal,
|
|
132
143
|
onEvent: turn.onEvent,
|
|
133
144
|
};
|
|
134
145
|
}
|
|
135
146
|
|
|
147
|
+
/** Server-side knobs the MCP child's NESTED classifier spawn needs (P3 propose_workflow, task mode). The chat's claude is
|
|
148
|
+
* spawned env-scrubbed, so nothing WORCA_* reaches the child unless it rides mcpServers.env. Forwarded only when set. */
|
|
149
|
+
export const MCP_FORWARD_ENV = Object.freeze(['WORCA_CLAUDE_BIN', 'ORCH_CLAUDE_BIN', 'WORCA_AUTO_MODEL']);
|
|
150
|
+
|
|
136
151
|
/**
|
|
137
152
|
* The per-turn --mcp-config document (spec §6.4). `homeBase` is the RAW base
|
|
138
153
|
* (path.resolve(process.env.WORCA_HOME) or dirname(worcaHome())) — never
|
|
139
154
|
* worcaHome() itself. The argv twins make the child independent of env forwarding.
|
|
140
155
|
*/
|
|
141
|
-
export function buildMcpConfig({ homeBase, threadId, execPath = process.execPath, serverPath }) {
|
|
156
|
+
export function buildMcpConfig({ homeBase, threadId, execPath = process.execPath, serverPath, env = process.env }) {
|
|
142
157
|
if (!serverPath) throw new Error('buildMcpConfig: serverPath is required');
|
|
143
158
|
if (typeof homeBase !== 'string' || !homeBase.trim()) throw new Error('buildMcpConfig: homeBase is required');
|
|
144
159
|
const base = resolvePath(homeBase);
|
|
145
160
|
const thread = String(threadId ?? '');
|
|
161
|
+
const forwarded = {};
|
|
162
|
+
for (const k of MCP_FORWARD_ENV) if (env && typeof env[k] === 'string' && env[k] !== '') forwarded[k] = env[k];
|
|
146
163
|
return {
|
|
147
164
|
mcpServers: {
|
|
148
165
|
worca: {
|
|
149
166
|
type: 'stdio',
|
|
150
167
|
command: execPath,
|
|
151
168
|
args: ['--disable-warning=ExperimentalWarning', serverPath, '--home', base, '--thread', thread],
|
|
152
|
-
env: { WORCA_HOME: base, WORCA_ASK_THREAD_ID: thread },
|
|
169
|
+
env: { WORCA_HOME: base, WORCA_ASK_THREAD_ID: thread, ...forwarded },
|
|
153
170
|
},
|
|
154
171
|
},
|
|
155
172
|
};
|
package/src/core/ask/store.mjs
CHANGED
|
@@ -261,22 +261,30 @@ export function findCard(threadId, cardId) {
|
|
|
261
261
|
return null;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
const CARD_PATCH_KEYS = ['state', 'runId', 'error'];
|
|
264
|
+
const CARD_PATCH_KEYS = ['state', 'runId', 'error', 'workflowId'];
|
|
265
265
|
|
|
266
|
-
/** Patch ⊆ {state, runId, error} on one card block
|
|
266
|
+
/** Patch ⊆ {state, runId, error, workflowId} on one card block. A WORKFLOW card (card.type === 'workflow') and a
|
|
267
|
+
* METRICS card (card.type === 'metrics', whose `result` lands at Apply) also take a SHALLOW `card` sub-patch; a run
|
|
268
|
+
* card's `card` is never touched (its key set is pinned). The 'proposed' precondition is the caller's (route) business. */
|
|
267
269
|
export function updateCardBlock(threadId, cardId, patch = {}) {
|
|
268
270
|
return tx(() => {
|
|
269
271
|
const found = findCard(threadId, cardId);
|
|
270
272
|
if (!found) return null;
|
|
271
273
|
const allowed = {};
|
|
272
274
|
for (const k of CARD_PATCH_KEYS) if (Object.prototype.hasOwnProperty.call(patch, k)) allowed[k] = patch[k];
|
|
273
|
-
const
|
|
275
|
+
const sub = patch.card && typeof patch.card === 'object' && !Array.isArray(patch.card) ? patch.card : null;
|
|
276
|
+
const blocks = found.message.blocks.map((b) => {
|
|
277
|
+
if (!(b && b.kind === 'card' && b.id === cardId)) return b;
|
|
278
|
+
const subPatchable = !!(b.card && (b.card.type === 'workflow' || b.card.type === 'metrics'));
|
|
279
|
+
return { ...b, ...allowed, ...(sub && subPatchable ? { card: { ...(b.card || {}), ...sub } } : {}) };
|
|
280
|
+
});
|
|
274
281
|
prepare('UPDATE ask_messages SET blocks = ? WHERE id = ?').run(JSON.stringify(blocks), found.message.id);
|
|
275
282
|
return blocks.find((b) => b && b.kind === 'card' && b.id === cardId);
|
|
276
283
|
});
|
|
277
284
|
}
|
|
278
285
|
|
|
279
|
-
/** Boot sweep (spec §6.2): a turn the previous server process never finished.
|
|
286
|
+
/** Boot sweep (spec §6.2): a turn the previous server process never finished. A workflow card still
|
|
287
|
+
* `building` in such a row can never flip (its turn is gone) — it fails with the same text. */
|
|
280
288
|
export function sweepStreamingMessages({ text = 'interrupted by restart' } = {}) {
|
|
281
289
|
return tx(() => {
|
|
282
290
|
const rows = prepare("SELECT id, blocks FROM ask_messages WHERE status = 'streaming'").all();
|
|
@@ -284,7 +292,8 @@ export function sweepStreamingMessages({ text = 'interrupted by restart' } = {})
|
|
|
284
292
|
// The whole sweep is ONE tx(): a TypeError on a single poisoned row would roll
|
|
285
293
|
// back every other row's fix, and would do so again on every later boot.
|
|
286
294
|
const prev = parse(r.blocks, []);
|
|
287
|
-
const blocks = Array.isArray(prev) ? prev : []
|
|
295
|
+
const blocks = (Array.isArray(prev) ? prev : [])
|
|
296
|
+
.map((b) => (b && b.kind === 'card' && b.state === 'building' ? { ...b, state: 'failed', error: text } : b));
|
|
288
297
|
blocks.push({ kind: 'notice', text });
|
|
289
298
|
prepare("UPDATE ask_messages SET status = 'error', blocks = ? WHERE id = ?").run(JSON.stringify(blocks), r.id);
|
|
290
299
|
}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
import { readFile, access } from 'node:fs/promises';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import {
|
|
8
|
-
listAllPipelines, lookupPipelineRow, findPipelineRowById, totalsFor, readStoreMeta, runDirForRow,
|
|
8
|
+
listAllPipelines, lookupPipelineRow, findPipelineRowById, totalsFor, readStoreMeta, runDirForRow, readMemoryLedger,
|
|
9
|
+
listRunArtifacts, readRunProgress, resolveIndexedArtifactForRow,
|
|
9
10
|
} from '../artifacts.mjs';
|
|
10
11
|
import { DIFF_PATCH_FILE } from '../results.mjs';
|
|
11
12
|
import { GUARDRAIL_PRESETS } from '../guardrails.mjs';
|
|
12
13
|
import { buildCatalog } from './catalog.mjs';
|
|
13
14
|
import { validateProposal } from './proposal.mjs';
|
|
14
|
-
import { readAttachmentText, getAttachment, attachmentPath, getThread } from './store.mjs';
|
|
15
|
+
import { readAttachmentText, getAttachment, attachmentPath, getThread, listAttachments } from './store.mjs';
|
|
15
16
|
import { redactAskText } from './redact.mjs';
|
|
16
17
|
import { ASK_LIMITS } from './limits.mjs';
|
|
17
18
|
|
|
@@ -35,6 +36,17 @@ export async function hasDiffPatch(row) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/** The run's memory changes from its ledger (artifacts.mjs#readMemoryLedger) as { changes, totals },
|
|
40
|
+
* or null when the run has none — get_run then carries no `memory` key at all. The mount path is
|
|
41
|
+
* dropped: it is a directory of THIS machine, never something the model should see. Read-only. */
|
|
42
|
+
export async function readRunMemory(row) {
|
|
43
|
+
try {
|
|
44
|
+
const ledger = await readMemoryLedger(await runDirForRow(row));
|
|
45
|
+
if (!ledger || !ledger.changes.length) return null;
|
|
46
|
+
return { changes: ledger.changes, totals: ledger.totals };
|
|
47
|
+
} catch { return null; }
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
/**
|
|
39
51
|
* @param {{threadId:string}} opts attachments are readable only for this thread (spec §6.4 read_attachment)
|
|
40
52
|
*/
|
|
@@ -48,6 +60,10 @@ export function defaultToolDeps({ threadId }) {
|
|
|
48
60
|
readStoreMeta,
|
|
49
61
|
readDiffPatch,
|
|
50
62
|
hasDiffPatch,
|
|
63
|
+
readRunMemory,
|
|
64
|
+
listRunArtifacts: (row, filter) => listRunArtifacts(row.id, filter),
|
|
65
|
+
readRunArtifact: (row, rel) => resolveIndexedArtifactForRow(row, rel), // {rel, text}|null
|
|
66
|
+
readRunProgress: (row) => readRunProgress(row.id),
|
|
51
67
|
readAttachment: (id) => {
|
|
52
68
|
const row = threadId ? getAttachment(threadId, id) : null;
|
|
53
69
|
if (!row) return null;
|
|
@@ -64,6 +80,14 @@ export function defaultToolDeps({ threadId }) {
|
|
|
64
80
|
return path ? { name: row.name, kind: row.kind, mime: row.mime, bytes: row.bytes, path } : null;
|
|
65
81
|
},
|
|
66
82
|
validateProposal,
|
|
83
|
+
// The run card's attachment pills (propose_run attachmentIds): the ledger of
|
|
84
|
+
// the owning thread only — never another thread's files. An unreadable DB means
|
|
85
|
+
// "no attachments", never an error: tools.call does not catch handler throws, so
|
|
86
|
+
// a locked store would otherwise make the model unable to propose at all.
|
|
87
|
+
listAttachments: () => {
|
|
88
|
+
if (!threadId) return [];
|
|
89
|
+
try { return listAttachments(threadId); } catch { return []; }
|
|
90
|
+
},
|
|
67
91
|
// #397: the user-pinned scope of the owning thread — {projectKey}|{workspaceId}|
|
|
68
92
|
// null — read fresh from the thread row per call, so a selector change lands on
|
|
69
93
|
// the very next tool call. A missing thread or an unreadable DB means "nothing
|