@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.
- package/README.md +85 -6
- package/agents/clarify.meta.json +1 -0
- package/agents/memoryDefragmenter.meta.json +2 -1
- package/agents/reviewer.meta.json +60 -0
- package/agents/worca-cc-code-reviewer.md +33 -0
- package/agents/worca-cc-memory-defragmenter.md +5 -3
- package/agents/workspaceScanner.meta.json +1 -0
- package/package.json +14 -10
- package/scripts/git-diff.mjs +25 -0
- package/scripts/gitDiff.meta.json +18 -0
- package/scripts/js-inline.mjs +11 -0
- package/scripts/js.meta.json +22 -0
- package/scripts/py-inline.py +27 -0
- package/scripts/py.meta.json +22 -0
- package/scripts/shell.meta.json +24 -0
- package/skills/worca/SKILL.md +3 -2
- package/src/cli/models.mjs +247 -0
- package/src/cli/render.mjs +72 -4
- package/src/cli/schedule.mjs +494 -0
- package/src/cli/worca-cc.mjs +1001 -22
- package/src/core/agent-registry.mjs +75 -23
- package/src/core/agent-store.mjs +51 -2
- package/src/core/artifacts.mjs +73 -10
- package/src/core/ask/events.mjs +119 -1
- package/src/core/ask/limits.mjs +32 -4
- package/src/core/ask/mcp-stdio.mjs +12 -0
- package/src/core/ask/model-deps.mjs +126 -0
- package/src/core/ask/model-proposal.mjs +370 -0
- package/src/core/ask/models.mjs +12 -0
- package/src/core/ask/policy-deps.mjs +124 -0
- package/src/core/ask/policy-proposal.mjs +363 -0
- package/src/core/ask/prompt.mjs +74 -9
- package/src/core/ask/proposal.mjs +54 -5
- package/src/core/ask/schedule-deps.mjs +83 -0
- package/src/core/ask/schedule-spec.mjs +310 -0
- package/src/core/ask/script-deps.mjs +357 -0
- package/src/core/ask/source-deps.mjs +52 -0
- package/src/core/ask/source-spec.mjs +157 -0
- package/src/core/ask/spawn.mjs +1 -0
- package/src/core/ask/store.mjs +6 -3
- package/src/core/ask/tool-deps.mjs +4 -0
- package/src/core/ask/tools.mjs +657 -37
- package/src/core/ask/turn.mjs +109 -2
- package/src/core/ask-files.mjs +406 -0
- package/src/core/ask-forms.mjs +195 -0
- package/src/core/ask-projection.mjs +72 -0
- package/src/core/bridge/errors.mjs +84 -0
- package/src/core/bridge/provider-ops.mjs +281 -0
- package/src/core/bridge/providers/copilot.mjs +269 -0
- package/src/core/bridge/providers/endpoint.mjs +257 -0
- package/src/core/bridge/registry.mjs +88 -0
- package/src/core/bridge/semaphore.mjs +73 -0
- package/src/core/bridge/server.mjs +184 -0
- package/src/core/bridge/telemetry.mjs +53 -0
- package/src/core/bridge/translate/request.mjs +252 -0
- package/src/core/bridge/translate/response.mjs +82 -0
- package/src/core/bridge/translate/stream.mjs +242 -0
- package/src/core/bridge/upstream.mjs +209 -0
- package/src/core/chat/command-router.mjs +58 -4
- package/src/core/chat/notifier.mjs +14 -1
- package/src/core/chat/renderers.mjs +35 -0
- package/src/core/claude-runner.mjs +126 -19
- package/src/core/config.mjs +212 -31
- package/src/core/cost-budget.mjs +3 -2
- package/src/core/db.mjs +169 -15
- package/src/core/failure-policy.mjs +10 -0
- package/src/core/fs-browse.mjs +16 -4
- package/src/core/git-info.mjs +22 -0
- package/src/core/graph/builtin-workflows.mjs +3 -1
- package/src/core/graph/exec-io.mjs +71 -0
- package/src/core/graph/executor.mjs +139 -70
- package/src/core/graph/human-evidence.mjs +131 -0
- package/src/core/graph/python-probe.mjs +172 -0
- package/src/core/graph/registry-ports.mjs +10 -6
- package/src/core/graph/scheduler.mjs +39 -24
- package/src/core/graph/script-child.mjs +81 -0
- package/src/core/graph/script-runner.mjs +597 -0
- package/src/core/graph/worca_script.py +207 -0
- package/src/core/guardrail-store.mjs +16 -0
- package/src/core/human-backfill.mjs +108 -0
- package/src/core/human-rate.mjs +17 -0
- package/src/core/index-html.mjs +6 -2
- package/src/core/memory-defrag-model.mjs +112 -0
- package/src/core/memory-store.mjs +70 -18
- package/src/core/memory-sync.mjs +22 -11
- package/src/core/metrics/read.mjs +4 -1
- package/src/core/metrics/record.mjs +50 -2
- package/src/core/metrics/sync.mjs +6 -4
- package/src/core/model-env.mjs +149 -0
- package/src/core/model-test.mjs +14 -1
- package/src/core/notifications.mjs +128 -0
- package/src/core/onboarding.mjs +8 -2
- package/src/core/orchestrator.mjs +357 -26
- package/src/core/phases.mjs +95 -6
- package/src/core/plugin-api.mjs +24 -7
- package/src/core/plugin-manifest.mjs +184 -18
- package/src/core/plugin-models.mjs +1 -0
- package/src/core/plugin-script-cases.mjs +118 -0
- package/src/core/plugin-store.mjs +163 -17
- package/src/core/plugin-workflows.mjs +71 -17
- package/src/core/policy/cache.mjs +116 -0
- package/src/core/policy/effective.mjs +175 -0
- package/src/core/policy/gate.mjs +91 -0
- package/src/core/policy/local.mjs +145 -0
- package/src/core/policy/registry.mjs +330 -0
- package/src/core/policy/scope.mjs +61 -0
- package/src/core/policy/state.mjs +79 -0
- package/src/core/policy/sync.mjs +513 -0
- package/src/core/protocol.mjs +43 -0
- package/src/core/run-harness.mjs +421 -72
- package/src/core/scheduler.mjs +980 -0
- package/src/core/script-bench.mjs +628 -0
- package/src/core/script-registry.mjs +116 -0
- package/src/core/script-store.mjs +563 -0
- package/src/core/settings.mjs +489 -14
- package/src/core/stats.mjs +33 -2
- package/src/core/workflow-export.mjs +94 -3
- package/src/core/workflow-share.mjs +67 -18
- package/src/core/workflows.mjs +47 -11
- package/src/core/workspaces.mjs +18 -12
- package/src/shared/forms/answer.mjs +164 -0
- package/src/shared/forms/catalog.mjs +91 -0
- package/src/shared/forms/form-def.mjs +290 -0
- package/src/shared/forms/layout.mjs +67 -0
- package/src/shared/forms/paths.mjs +47 -0
- package/src/shared/forms/project.mjs +309 -0
- package/src/shared/forms/schema.mjs +205 -0
- package/src/shared/graph/agent-meta.mjs +55 -5
- package/src/shared/graph/constants.mjs +14 -2
- package/src/shared/graph/flow-layout.mjs +2 -1
- package/src/shared/graph/isomorphic.mjs +5 -3
- package/src/shared/graph/manifest.mjs +22 -13
- package/src/shared/graph/ports.mjs +45 -19
- package/src/shared/graph/script-cases.mjs +257 -0
- package/src/shared/graph/script-icons.mjs +46 -0
- package/src/shared/graph/script-infer.mjs +259 -0
- package/src/shared/graph/script-meta.mjs +408 -0
- package/src/shared/graph/script-templates.mjs +201 -0
- package/src/shared/graph/template.mjs +4 -4
- package/src/shared/graph/validate.mjs +89 -16
- package/src/shared/human-estimate.mjs +100 -0
- package/src/shared/schedule/recurrence.mjs +353 -0
- package/src/shared/team-metrics/aggregate.mjs +51 -11
- package/{scripts → tools}/install.mjs +3 -3
- package/ui/public/app.js +4390 -683
- package/ui/public/artifact-picker.mjs +189 -0
- package/ui/public/ask/dom.mjs +121 -0
- package/ui/public/ask/form-preview.mjs +55 -0
- package/ui/public/ask/form-renderer.mjs +250 -0
- package/ui/public/ask/registry.mjs +53 -0
- package/ui/public/ask/widgets-display.mjs +370 -0
- package/ui/public/ask/widgets-input.mjs +624 -0
- package/ui/public/ask/widgets-layout.mjs +90 -0
- package/ui/public/ask-panel.mjs +401 -27
- package/ui/public/ask-run-card.mjs +1 -1
- package/ui/public/bridge-view.mjs +694 -0
- package/ui/public/chat-settings-view.mjs +24 -0
- package/ui/public/code-editor.mjs +181 -0
- package/ui/public/getting-started.mjs +34 -7
- package/ui/public/graph/composer.mjs +138 -10
- package/ui/public/graph/inspector.mjs +61 -58
- package/ui/public/graph/palette.mjs +27 -9
- package/ui/public/graph/run-decor.mjs +34 -17
- package/ui/public/graph/run-hosts.mjs +7 -1
- package/ui/public/graph/save-dialog.mjs +3 -0
- package/ui/public/graph/view.mjs +23 -7
- package/ui/public/guardrails-view.mjs +15 -3
- package/ui/public/guide-spot.mjs +87 -9
- package/ui/public/index.html +480 -141
- package/ui/public/memory-view.mjs +22 -4
- package/ui/public/models-view.mjs +162 -17
- package/ui/public/node-tunables.mjs +33 -4
- package/ui/public/plugins-view.mjs +23 -1
- package/ui/public/results-view.mjs +4 -2
- package/ui/public/schedule-sheet.mjs +430 -0
- package/ui/public/schedules-view.mjs +432 -0
- package/ui/public/script-bench-view.mjs +1154 -0
- package/ui/public/script-forms.mjs +282 -0
- package/ui/public/script-wizard.mjs +529 -0
- package/ui/public/scripts-view.mjs +868 -0
- package/ui/public/stats-view.mjs +159 -52
- package/ui/public/style.css +1461 -44
- package/ui/public/team-metrics-surfaces.mjs +77 -16
- package/ui/public/team-metrics-view.mjs +68 -5
- package/ui/public/team-policy-view.mjs +1402 -0
- package/ui/public/ui-level.mjs +237 -0
- package/ui/server.mjs +1827 -73
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
// question there and STOPS (no role side effects); the
|
|
26
26
|
// resumed prompt carries no MOCK_ASK, so the role arm
|
|
27
27
|
// runs then.
|
|
28
|
+
// MOCK_ASK_FORM: <json> a ONE-LINE {"form","data"} payload. When present it is
|
|
29
|
+
// written verbatim instead of the canned {questions}
|
|
30
|
+
// body — to MOCK_ASK for a producer, to MOCK_OUT for the
|
|
31
|
+
// clarify role. Lets an offline mock agent exercise the
|
|
32
|
+
// ask-form protocol end to end.
|
|
28
33
|
//
|
|
29
34
|
// Markers are matched leniently: "KEY: value" anywhere at the start of a line,
|
|
30
35
|
// case-sensitive keys, value trimmed. Missing markers degrade gracefully.
|
|
@@ -346,9 +351,11 @@ export function mockEnabled(opts) {
|
|
|
346
351
|
* @param {number} [o.maxTurns] --max-turns <n> (positive safe integer; else omitted)
|
|
347
352
|
* @param {number|null} [o.maxBudgetUsd] --max-budget-usd <n> (finite > 0; null/else omitted)
|
|
348
353
|
* @param {string} [o.appendSubagentSystemPrompt] --append-subagent-system-prompt <text> (Task children only)
|
|
349
|
-
* @param {string[]} [o.addDirs] --add-dir <dir> per entry (Ask Worca's memory mount; the CLI loads <dir>/.claude/rules only with CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 in the env — memory-deps.mjs / spawn.mjs set it)
|
|
354
|
+
* @param {string[]} [o.addDirs] --add-dir <dir> per entry (Ask Worca's memory mount and every pipeline spawn's writable memory copy; the CLI loads <dir>/.claude/rules only with CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 in the env — memory-deps.mjs / spawn.mjs set it)
|
|
350
355
|
* All nine are Ask Worca sandbox options (ask-worca-design.md §6.3) and default-off.
|
|
351
356
|
* @param {number} [o.argvInlineLimit] override ARGV_INLINE_LIMIT (GH #380; tests force the staged path)
|
|
357
|
+
* @param {string[]} [o.disallowedTools] --disallowedTools <list>: built-ins withheld from this spawn
|
|
358
|
+
* (model bridge §5.3: WebSearch/WebFetch for a translated model). Absent/empty ⇒ flag omitted.
|
|
352
359
|
* @returns {Promise<{text:string, exitCode:number}>}
|
|
353
360
|
*/
|
|
354
361
|
export async function runClaude(o = {}) {
|
|
@@ -373,6 +380,7 @@ export async function runClaude(o = {}) {
|
|
|
373
380
|
envScrub,
|
|
374
381
|
envAllowlist,
|
|
375
382
|
modelEnv,
|
|
383
|
+
disallowedTools,
|
|
376
384
|
workspaceWriteTargets,
|
|
377
385
|
resumeSessionId,
|
|
378
386
|
// Ask Worca sandbox hardening (ask-worca-design.md §6.3/§6.8). All default-off:
|
|
@@ -422,6 +430,7 @@ export async function runClaude(o = {}) {
|
|
|
422
430
|
envScrub,
|
|
423
431
|
envAllowlist,
|
|
424
432
|
modelEnv,
|
|
433
|
+
disallowedTools,
|
|
425
434
|
tools,
|
|
426
435
|
strictMcpConfig,
|
|
427
436
|
settingSources,
|
|
@@ -453,9 +462,10 @@ export async function runClaude(o = {}) {
|
|
|
453
462
|
* (docs/run-root-verification.md, branch (a); argv-attested
|
|
454
463
|
* transcript phase0/out/v1a-rerun.jsonl, with a no-grant
|
|
455
464
|
* negative control proving the grant is load-bearing).
|
|
456
|
-
* `--add-dir` carries Ask Worca's memory mount
|
|
457
|
-
* CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1
|
|
458
|
-
*
|
|
465
|
+
* `--add-dir` carries Ask Worca's memory mount (with the
|
|
466
|
+
* CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 override, which is what makes the CLI LOAD rules
|
|
467
|
+
* from it) and, since the memory write split, every pipeline spawn's writable memory copy (no
|
|
468
|
+
* override: nothing is loaded from it, it is only made editable under acceptEdits). */
|
|
459
469
|
export function buildClaudeArgs({
|
|
460
470
|
prompt, systemPrompt, permissionMode, model, effort, allowedTools, resumeSessionId,
|
|
461
471
|
mcpConfigPath, mcpServerGrants, permissionRules,
|
|
@@ -463,7 +473,7 @@ export function buildClaudeArgs({
|
|
|
463
473
|
// way in because the legacy body below already owns a local `tools` (the
|
|
464
474
|
// --allowedTools union).
|
|
465
475
|
tools: builtinTools, strictMcpConfig, settingSources, disableSlashCommands, includePartialMessages,
|
|
466
|
-
maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, hostGuard, addDirs,
|
|
476
|
+
maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, hostGuard, addDirs, disallowedTools,
|
|
467
477
|
}, delivery = {}) {
|
|
468
478
|
// delivery (GH #380, set only by planClaudeInvocation's staged branch):
|
|
469
479
|
// promptViaStdin -> bare `-p`; the prompt is written to the child's stdin
|
|
@@ -495,6 +505,12 @@ export function buildClaudeArgs({
|
|
|
495
505
|
if (tools.length) {
|
|
496
506
|
args.push('--allowedTools', tools.join(','));
|
|
497
507
|
}
|
|
508
|
+
// Model bridge (model-bridge-design.md §5.3): a translated (openai-chat)
|
|
509
|
+
// model has no server-side web tools, so the runner withholds them outright —
|
|
510
|
+
// a deny outranks any allow, frontmatter grant included. Absent/empty ⇒
|
|
511
|
+
// nothing emitted, so every other argv stays byte-identical.
|
|
512
|
+
const denied = Array.isArray(disallowedTools) ? disallowedTools.filter((s) => typeof s === 'string' && s) : [];
|
|
513
|
+
if (denied.length) args.push('--disallowedTools', denied.join(','));
|
|
498
514
|
// ── Ask Worca hardening flags (ask-worca-design.md §6.3 / §6.8) ──────────────
|
|
499
515
|
// Every one is default-off: absent / false / invalid ⇒ NOTHING is emitted, so
|
|
500
516
|
// every legacy argv stays byte-identical (test/spawn-args.test.mjs). Appended
|
|
@@ -579,7 +595,7 @@ export function stageClaudeInvocation(opts, { bin = DEFAULT_BIN, limit = ARGV_IN
|
|
|
579
595
|
return { ...plan, dir };
|
|
580
596
|
}
|
|
581
597
|
|
|
582
|
-
function runReal({ cwd, systemPrompt, prompt, allowedTools, permissionMode, model, effort, onEvent, signal, bin, resumeSessionId, mcpConfigPath, mcpServerGrants, permissionRules, envScrub, envAllowlist, modelEnv, tools, strictMcpConfig, settingSources, disableSlashCommands, includePartialMessages, maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, addDirs, argvInlineLimit }) {
|
|
598
|
+
function runReal({ cwd, systemPrompt, prompt, allowedTools, permissionMode, model, effort, onEvent, signal, bin, resumeSessionId, mcpConfigPath, mcpServerGrants, permissionRules, envScrub, envAllowlist, modelEnv, disallowedTools, tools, strictMcpConfig, settingSources, disableSlashCommands, includePartialMessages, maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, addDirs, argvInlineLimit }) {
|
|
583
599
|
return new Promise((resolveP, rejectP) => {
|
|
584
600
|
// Per-model routing env (design §4.4), prepared BEFORE argv: reserved keys
|
|
585
601
|
// are re-dropped here defensively — the write path already rejects them, so
|
|
@@ -658,7 +674,7 @@ function runReal({ cwd, systemPrompt, prompt, allowedTools, permissionMode, mode
|
|
|
658
674
|
permissionMode, model: wireModel, effort, allowedTools, resumeSessionId,
|
|
659
675
|
mcpConfigPath, mcpServerGrants, permissionRules,
|
|
660
676
|
tools, strictMcpConfig, settingSources, disableSlashCommands, includePartialMessages,
|
|
661
|
-
maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, addDirs,
|
|
677
|
+
maxTurns, maxBudgetUsd, appendSubagentSystemPrompt, addDirs, disallowedTools,
|
|
662
678
|
}, { bin: resolved.bin, limit });
|
|
663
679
|
} catch (err) {
|
|
664
680
|
rejectP(new Error(`Failed to stage the claude prompt files: ${err.message}`));
|
|
@@ -946,6 +962,17 @@ function parseMarkers(prompt, systemPrompt) {
|
|
|
946
962
|
return markers;
|
|
947
963
|
}
|
|
948
964
|
|
|
965
|
+
/** Parse a MOCK_*_FORM marker's one-line JSON, or null. A malformed marker must
|
|
966
|
+
* degrade to the canned body, never throw inside the mock. */
|
|
967
|
+
function tryParseMockJson(raw) {
|
|
968
|
+
try {
|
|
969
|
+
const v = JSON.parse(String(raw));
|
|
970
|
+
return v && typeof v === 'object' ? v : null;
|
|
971
|
+
} catch {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
949
976
|
async function ensureDir(filePath) {
|
|
950
977
|
await mkdir(dirname(filePath), { recursive: true });
|
|
951
978
|
}
|
|
@@ -1085,21 +1112,51 @@ async function mockAsk({ markers, prompt, cwd, onEvent, signal, resumeSessionId
|
|
|
1085
1112
|
const wfEvent = /^\s*\[worca event\] workflow card (card_[0-9a-f]{8}) (?:(declined)|saved as (\S+) "([^"]*)"; thenRun=(true|false))/.exec(userText);
|
|
1086
1113
|
// A metrics-card EVENT, then the metrics trigger — both before the run arm, whose \brun\b would otherwise fire on
|
|
1087
1114
|
// "include my runs"-style prose (it does not, \b stops at the s, but "propose" would).
|
|
1088
|
-
const tmEvent = /^\s*\[worca event\] metrics card (card_[0-9a-f]{8}) (applied|declined|failed)/.exec(userText);
|
|
1115
|
+
const tmEvent = /^\s*\[worca event\] (?:metrics|policy|model) card (card_[0-9a-f]{8}) (applied|declined|failed)/.exec(userText);
|
|
1089
1116
|
// The metrics arm wants a CHANGE, not a question: "metrics" plus a verb of intent ("stop recording my metrics",
|
|
1090
1117
|
// "route ... to the metrics home"). A bare "which workspaces use team metrics?" gets the generic echo answer.
|
|
1091
1118
|
const metrics = !wfEvent && !tmEvent && /\bmetrics\b/i.test(userText)
|
|
1092
1119
|
&& /\b(?:stop|start|turn|toggle|switch|record\w*|route|change|enable|disable|set)\b/i.test(userText);
|
|
1093
|
-
|
|
1120
|
+
// Scheduled runs (docs/scheduled-runs.md "Ask Worca"): a schedule-card EVENT; a CHANGE to an existing schedule
|
|
1121
|
+
// (its id in the text); or a new run to schedule ("schedule …"). All before the run arm, whose \brun\b would fire.
|
|
1122
|
+
const scEvent = /^\s*\[worca event\] schedule card (card_[0-9a-f]{8}) (applied|declined|failed)/.exec(userText);
|
|
1123
|
+
const scId = /\b(sch_[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/i.exec(userText);
|
|
1124
|
+
const scChange = !wfEvent && !tmEvent && !scEvent && !!scId && /\b(?:run now|move|delete|cancel|edit|change)\b/i.test(userText);
|
|
1125
|
+
const scNew = !wfEvent && !tmEvent && !scEvent && !scChange && /\bschedul/i.test(userText);
|
|
1126
|
+
// The team-policy arm, the metrics rule: "policy" plus a verb of intent ("raise the policy cap to $30").
|
|
1127
|
+
// It proposes an edit of the context project's per-pipeline cap: `$<n>` in the text, else $30.
|
|
1128
|
+
const policy = !wfEvent && !tmEvent && !metrics && !scEvent && !scNew && !scChange && /\bpolicy\b/i.test(userText)
|
|
1129
|
+
&& /\b(?:raise|lower|set|change|edit|make|cap)\b/i.test(userText);
|
|
1130
|
+
// A tracker task named by key ("fix jira bug PROJ-123"): the run's task is the issue (mock-source's
|
|
1131
|
+
// fixture plugin), and "auto" asks for the Auto workflow. Folds into the schedule and run arms.
|
|
1132
|
+
const taskKey = !wfEvent && !tmEvent && !scEvent && /\b(?:issue|ticket|bug|task)\b/i.test(userText) ? /\b([A-Z][A-Z0-9]+-\d+)\b/.exec(userText) : null;
|
|
1133
|
+
const wantsAuto = /\bauto\b/i.test(userText);
|
|
1134
|
+
const workflow = !wfEvent && !tmEvent && !metrics && !policy && !scNew && !scChange && !taskKey && /\bworkflow\b/i.test(userText);
|
|
1094
1135
|
const agents = !wfEvent && !tmEvent && /\bagents?\b/i.test(userText);
|
|
1095
|
-
|
|
1136
|
+
// Models (docs/models.md "Ask Worca"): "add a local llama model" proposes a keyless llama.cpp entry; "remove model
|
|
1137
|
+
// <id>" its removal. The parent re-validates the INPUT against the real catalog and mints the card.
|
|
1138
|
+
const modelAdd = !wfEvent && !tmEvent && !scEvent && /\bllama\b/i.test(userText) && /\b(?:add|register)\b/i.test(userText);
|
|
1139
|
+
const modelRemove = !wfEvent && !tmEvent && !scEvent && !modelAdd ? /\bremove model ([A-Za-z0-9._-]+)/i.exec(userText) : null;
|
|
1140
|
+
const propose = !wfEvent && !tmEvent && !scEvent && !workflow && !metrics && !policy && !scNew && !scChange && !modelAdd && !modelRemove && (!!taskKey || /\b(propose|start|run)\b/i.test(userText));
|
|
1141
|
+
// The proposal both arms send: a brief, or the task reference instead of one.
|
|
1142
|
+
const proposal = () => {
|
|
1143
|
+
if (!taskKey) return { ...card, ...(wantsAuto ? { workflowId: 'wf_auto' } : {}) };
|
|
1144
|
+
const { brief: _brief, ...rest } = card;
|
|
1145
|
+
return { ...rest, ...(wantsAuto ? { workflowId: 'wf_auto' } : {}), source: { plugin: 'mock-source', sourceId: 'mock', taskId: taskKey[1] }, note: 'mock: the run reads the issue when it starts' };
|
|
1146
|
+
};
|
|
1147
|
+
const taskFrames = (MSG) => (taskKey ? [
|
|
1148
|
+
atool(MSG, 'toolu_mock_sources', 'mcp__worca__list_task_sources', {}),
|
|
1149
|
+
uresult('toolu_mock_sources', JSON.stringify({ sources: [{ plugin: 'mock-source', sourceId: 'mock', displayName: 'Mock Tasks' }] })),
|
|
1150
|
+
atool(MSG, 'toolu_mock_find', 'mcp__worca__find_tasks', { plugin: 'mock-source', sourceId: 'mock', search: taskKey[1] }),
|
|
1151
|
+
uresult('toolu_mock_find', JSON.stringify({ tasks: [{ id: taskKey[1], title: 'Mock task' }] })),
|
|
1152
|
+
] : []);
|
|
1096
1153
|
|
|
1097
1154
|
const SID = resumeSessionId || 'mock-session-ask-1';
|
|
1098
1155
|
const USAGE = { input_tokens: 10, output_tokens: 20, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 };
|
|
1099
1156
|
const firstLine = userText.split(/\r?\n/).map((l) => l.trim()).find(Boolean) || '';
|
|
1100
1157
|
const ANSWER = `[mock] ${firstLine.slice(0, 200)}`;
|
|
1101
1158
|
const init = { type: 'system', subtype: 'init', session_id: SID, cwd, model: 'mock', permissionMode: 'dontAsk',
|
|
1102
|
-
tools: ['Task', 'mcp__worca__list_runs', 'mcp__worca__get_run', 'mcp__worca__propose_run', 'mcp__worca__propose_workflow', 'mcp__worca__propose_metrics_change'],
|
|
1159
|
+
tools: ['Task', 'mcp__worca__list_runs', 'mcp__worca__get_run', 'mcp__worca__propose_run', 'mcp__worca__propose_workflow', 'mcp__worca__propose_metrics_change', 'mcp__worca__propose_policy_change'],
|
|
1103
1160
|
mcp_servers: [{ name: 'worca', status: 'connected' }], plugins: [], skills: [], slash_commands: [], agents: [], uuid: 'mock-uuid-init' };
|
|
1104
1161
|
const mstart = (id) => ({ type: 'stream_event', event: { type: 'message_start', message: { id, model: 'mock', role: 'assistant', content: [], usage: USAGE } }, parent_tool_use_id: null, session_id: SID });
|
|
1105
1162
|
const delta = (t) => ({ type: 'stream_event', event: { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: t } }, parent_tool_use_id: null, session_id: SID });
|
|
@@ -1155,6 +1212,39 @@ async function mockAsk({ markers, prompt, cwd, onEvent, signal, resumeSessionId
|
|
|
1155
1212
|
uresult('toolu_mock_metrics', JSON.stringify({ ok: true, card: { type: 'metrics', ...tmInput } })));
|
|
1156
1213
|
answerMsg = MSG2;
|
|
1157
1214
|
}
|
|
1215
|
+
if (policy) {
|
|
1216
|
+
const usd = Number((/\$\s*(\d+(?:\.\d+)?)/.exec(userText) || [])[1] || 30);
|
|
1217
|
+
const tpInput = { kind: 'edit', projectKey: card.projectKey || null, set: [{ key: 'cost.pipelineLimitUsd', value: usd, kind: 'soft' }], note: 'mock: change the team per-pipeline cap' };
|
|
1218
|
+
frames.push(delta('[mock] '), delta('proposing '), delta('a policy change'), atext(MSG1, 'Proposing a policy change card.'),
|
|
1219
|
+
atool(MSG1, 'toolu_mock_policy', 'mcp__worca__propose_policy_change', tpInput),
|
|
1220
|
+
uresult('toolu_mock_policy', JSON.stringify({ ok: true, card: { type: 'policy', ...tpInput } })));
|
|
1221
|
+
answerMsg = MSG2;
|
|
1222
|
+
}
|
|
1223
|
+
if (scNew) {
|
|
1224
|
+
// The parent re-validates the INPUT with the real validator: "every" in the text makes a weekday series,
|
|
1225
|
+
// anything else a one-off two minutes out (short enough to watch the server start it).
|
|
1226
|
+
const every = /\bevery\b/i.test(userText);
|
|
1227
|
+
const input = { ...proposal(), ...(every ? { every: 'weekdays 02:00' } : { when: '+2m' }) };
|
|
1228
|
+
frames.push(...taskFrames(MSG1));
|
|
1229
|
+
frames.push(delta('[mock] '), delta('scheduling '), delta('a run'), atext(MSG1, every ? 'Scheduling it every weekday at 02:00.' : 'Scheduling it two minutes from now.'),
|
|
1230
|
+
atool(MSG1, 'toolu_mock_preview', 'mcp__worca__preview_schedule', every ? { every: 'weekdays 02:00' } : { when: '+2m' }),
|
|
1231
|
+
uresult('toolu_mock_preview', JSON.stringify({ ok: true, kind: every ? 'repeat' : 'once' })),
|
|
1232
|
+
atool(MSG1, 'toolu_mock_propose', 'mcp__worca__propose_run', input), uresult('toolu_mock_propose', JSON.stringify({ ok: true })));
|
|
1233
|
+
answerMsg = MSG2;
|
|
1234
|
+
}
|
|
1235
|
+
if (scChange) {
|
|
1236
|
+
const t = userText.toLowerCase();
|
|
1237
|
+
const action = /run now/.test(t) ? 'run_now' : /\bmove\b/.test(t) ? 'move' : /\bdelete\b/.test(t) ? 'delete' : /\bcancel\b/.test(t) ? 'cancel' : 'edit';
|
|
1238
|
+
const input = { id: scId[1], action, ...(action === 'move' ? { when: '+5m' } : action === 'edit' ? { every: 'weekdays 03:00' } : {}), note: 'mock: as asked' };
|
|
1239
|
+
frames.push(delta('[mock] '), delta('proposing '), delta('a schedule change'), atext(MSG1, 'Proposing a schedule change card.'),
|
|
1240
|
+
atool(MSG1, 'toolu_mock_sched', 'mcp__worca__propose_schedule_change', input), uresult('toolu_mock_sched', JSON.stringify({ ok: true })));
|
|
1241
|
+
answerMsg = MSG2;
|
|
1242
|
+
}
|
|
1243
|
+
if (scEvent) {
|
|
1244
|
+
const line = scEvent[2] === 'declined' ? 'Declined — nothing changed.' : scEvent[2] === 'failed' ? 'The change failed; check the error and try again.' : 'Done.';
|
|
1245
|
+
frames.push(delta('[mock] '), delta(scEvent[2]), atext(MSG1, line));
|
|
1246
|
+
answerMsg = MSG2;
|
|
1247
|
+
}
|
|
1158
1248
|
if (tmEvent) {
|
|
1159
1249
|
const line = tmEvent[2] === 'declined' ? 'Declined — nothing changed.' : tmEvent[2] === 'failed' ? 'The change failed; check the error and try again.' : 'Applied.';
|
|
1160
1250
|
frames.push(delta('[mock] '), delta(tmEvent[2]), atext(MSG1, line));
|
|
@@ -1173,9 +1263,17 @@ async function mockAsk({ markers, prompt, cwd, onEvent, signal, resumeSessionId
|
|
|
1173
1263
|
}
|
|
1174
1264
|
answerMsg = MSG2;
|
|
1175
1265
|
}
|
|
1266
|
+
if (modelAdd || modelRemove) {
|
|
1267
|
+
const mInput = modelAdd
|
|
1268
|
+
? { kind: 'add_model', model: { id: 'local-llama', label: 'Local llama', upstream: { provider: 'openai', api: 'openai-chat', model: 'qwen', baseUrl: 'http://127.0.0.1:8080/v1', capabilities: { maxPromptTokens: 65536, maxOutputTokens: 8192 } } }, note: 'mock: a local llama.cpp server' }
|
|
1269
|
+
: { kind: 'remove_model', id: modelRemove[1], note: 'mock: as asked' };
|
|
1270
|
+
frames.push(delta('[mock] '), delta('proposing '), delta('a model change'), atext(MSG1, 'Proposing a model change card.'),
|
|
1271
|
+
atool(MSG1, 'toolu_mock_model', 'mcp__worca__propose_model_change', mInput), uresult('toolu_mock_model', JSON.stringify({ ok: true })));
|
|
1272
|
+
answerMsg = MSG2;
|
|
1273
|
+
}
|
|
1176
1274
|
if (propose) {
|
|
1177
|
-
frames.push(delta('[mock] '), delta('preparing '), delta('a run'), atext(MSG1, 'Preparing a run card.'),
|
|
1178
|
-
atool(MSG1, 'toolu_mock_propose', 'mcp__worca__propose_run',
|
|
1275
|
+
frames.push(delta('[mock] '), delta('preparing '), delta('a run'), atext(MSG1, 'Preparing a run card.'), ...taskFrames(MSG1),
|
|
1276
|
+
atool(MSG1, 'toolu_mock_propose', 'mcp__worca__propose_run', proposal()), uresult('toolu_mock_propose', JSON.stringify({ ok: true })));
|
|
1179
1277
|
answerMsg = MSG2;
|
|
1180
1278
|
}
|
|
1181
1279
|
if (answerMsg !== MSG1) frames.push(mstart(answerMsg));
|
|
@@ -1246,12 +1344,17 @@ async function runMock({ cwd, systemPrompt, prompt, onEvent, signal, resumeSessi
|
|
|
1246
1344
|
// session event above already fired, so the resume has a session id.
|
|
1247
1345
|
if (m.MOCK_ASK && permissionMode !== 'dontAsk') { // belt and braces: dontAsk already took the ask arm above
|
|
1248
1346
|
await ensureDir(m.MOCK_ASK);
|
|
1249
|
-
|
|
1347
|
+
// MOCK_ASK_FORM (ask-forms §4): a one-line {"form","data"} payload, written
|
|
1348
|
+
// verbatim. Unparseable => the canned questions body, so a typo degrades to
|
|
1349
|
+
// today's behaviour instead of writing garbage the gate then refuses.
|
|
1350
|
+
const formBody = m.MOCK_ASK_FORM ? tryParseMockJson(m.MOCK_ASK_FORM) : null;
|
|
1351
|
+
const body = formBody || {
|
|
1250
1352
|
questions: [{ id: 'q1', question: `Mock question from ${role}?`, options: ['Option A', 'Option B'], allowFreeText: true }],
|
|
1251
|
-
}
|
|
1353
|
+
};
|
|
1354
|
+
await writeFile(m.MOCK_ASK, JSON.stringify(body, null, 2) + '\n', 'utf8');
|
|
1252
1355
|
safeEmit(onEvent, { type: 'tool_use', text: `wrote ${m.MOCK_ASK}`, raw: { mock: true, file: m.MOCK_ASK } });
|
|
1253
1356
|
safeEmit(onEvent, { type: 'result', costUsd: 0, raw: { mock: true, type: 'result', total_cost_usd: 0 } });
|
|
1254
|
-
await emitLog(onEvent, `[mock] questions written; stopping for answers (role=${role})`);
|
|
1357
|
+
await emitLog(onEvent, `[mock] ${formBody ? 'form ask' : 'questions'} written; stopping for answers (role=${role})`);
|
|
1255
1358
|
return { text: '[mock] asked questions', exitCode: 0 };
|
|
1256
1359
|
}
|
|
1257
1360
|
|
|
@@ -1344,7 +1447,11 @@ async function mockClarify(m, cycle, onEvent) {
|
|
|
1344
1447
|
// orchestrator's clarify loop terminates naturally. This mirrors the real fix:
|
|
1345
1448
|
// the loop converges because answers are returned to the planner.
|
|
1346
1449
|
const hasPrior = Number(m.MOCK_PRIOR || '0') > 0;
|
|
1347
|
-
|
|
1450
|
+
// MOCK_ASK_FORM (ask-forms §4): a one-line {"form","data"} payload, written VERBATIM
|
|
1451
|
+
// to the answers port instead of the canned questions — the clarifier's half of the
|
|
1452
|
+
// marker (the producer's half is the MOCK_ASK arm in runMock). Unparseable => canned.
|
|
1453
|
+
const formBody = m.MOCK_ASK_FORM ? tryParseMockJson(m.MOCK_ASK_FORM) : null;
|
|
1454
|
+
const payload = formBody || (hasPrior
|
|
1348
1455
|
? { questions: [] }
|
|
1349
1456
|
: {
|
|
1350
1457
|
questions: [
|
|
@@ -1367,10 +1474,10 @@ async function mockClarify(m, cycle, onEvent) {
|
|
|
1367
1474
|
allowFreeText: true,
|
|
1368
1475
|
},
|
|
1369
1476
|
],
|
|
1370
|
-
};
|
|
1477
|
+
});
|
|
1371
1478
|
await emitLog(
|
|
1372
1479
|
onEvent,
|
|
1373
|
-
hasPrior
|
|
1480
|
+
formBody ? '[mock] clarifier asking with a form' : hasPrior
|
|
1374
1481
|
? '[mock] planner has no further questions'
|
|
1375
1482
|
: '[mock] planner asking one clarifying question',
|
|
1376
1483
|
);
|