@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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/cli/models.mjs
|
|
2
|
+
// `worca models …` (model-bridge-design.md §6.5): the provider operations the
|
|
3
|
+
// Providers card offers, for a terminal — sign in to GitHub Copilot with the
|
|
4
|
+
// device flow, sign out, import Copilot models into the catalog, list the
|
|
5
|
+
// catalog with its bridge facts, and show provider state. Pure helpers are
|
|
6
|
+
// exported for tests; cmdModels takes its I/O injected (the schedule.mjs
|
|
7
|
+
// precedent) so nothing here touches process.stdout directly.
|
|
8
|
+
|
|
9
|
+
import { createInterface } from 'node:readline';
|
|
10
|
+
import {
|
|
11
|
+
providersState, beginCopilotLogin, pollCopilotLogin, copilotLogout, acknowledgeTerms,
|
|
12
|
+
copilotModelsForImport, importCopilotModels, testProviderConnection, patchProvider,
|
|
13
|
+
endpointModelsForImport, importEndpointModels,
|
|
14
|
+
} from '../core/bridge/provider-ops.mjs';
|
|
15
|
+
import { listModels } from '../core/config.mjs';
|
|
16
|
+
|
|
17
|
+
export const MODELS_HELP = `worca models — the model catalog and its providers
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
worca models list Catalog with provider / bridge facts
|
|
21
|
+
worca models providers Provider state (never a token)
|
|
22
|
+
worca models login copilot [--accept-terms]
|
|
23
|
+
Sign in to GitHub Copilot (device flow)
|
|
24
|
+
worca models logout copilot Forget the Copilot sign-in
|
|
25
|
+
worca models import copilot [--all | --pick <id,id,…>] [--yes]
|
|
26
|
+
Add Copilot models to the catalog
|
|
27
|
+
worca models import openai [--base-url <url>] [--all | --pick <id,id,…>] [--yes]
|
|
28
|
+
Add what an OpenAI-compatible endpoint serves
|
|
29
|
+
(llama.cpp, Ollama, LM Studio, vLLM, a gateway)
|
|
30
|
+
worca models test <provider> Reachability + auth check (copilot|openai|anthropic)
|
|
31
|
+
worca models set <provider> <key>=<value> …
|
|
32
|
+
Patch a provider: accountType, maxConcurrent, baseUrl, apiKey
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
Copilot: GitHub allows Copilot only through supported clients and has suspended
|
|
36
|
+
access for automated use. \`login\` prints the notice; --accept-terms records the
|
|
37
|
+
acknowledgement non-interactively. Pipelines are automated, high-volume use.
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
/** The notice text, mirrored from ui/public/bridge-view.mjs COPILOT_TERMS (kept
|
|
41
|
+
* here too: the CLI must not import UI modules). */
|
|
42
|
+
export const COPILOT_TERMS_TEXT = [
|
|
43
|
+
'Using GitHub Copilot from Worca',
|
|
44
|
+
'',
|
|
45
|
+
"Worca will talk to GitHub Copilot through the same API GitHub's editor extensions use, identifying itself as an editor client. GitHub's terms allow Copilot only through supported clients, and GitHub has suspended Copilot access for automated or unsupported use. Pipelines are automated, high-volume use.",
|
|
46
|
+
'',
|
|
47
|
+
' • Your GitHub account, not Worca, carries this risk.',
|
|
48
|
+
' • Worca caps concurrent requests (configurable) and never signs in without you.',
|
|
49
|
+
' • Premium-request quotas apply per your plan; Worca shows usage but cannot enforce it.',
|
|
50
|
+
].join('\n');
|
|
51
|
+
|
|
52
|
+
/** Minimal flag parser for the subcommand (mirrors worca-cc.mjs pluginArgs). */
|
|
53
|
+
export function modelsArgs(argv, valueFlags = [], boolFlags = [], fail = (m) => { throw new Error(m); }) {
|
|
54
|
+
const out = { _: [] };
|
|
55
|
+
for (let i = 0; i < argv.length; i++) {
|
|
56
|
+
let a = argv[i];
|
|
57
|
+
let inline;
|
|
58
|
+
const eq = a.indexOf('=');
|
|
59
|
+
if (a.startsWith('--') && eq !== -1) { inline = a.slice(eq + 1); a = a.slice(0, eq); }
|
|
60
|
+
if (valueFlags.includes(a)) {
|
|
61
|
+
const v = inline !== undefined ? inline : argv[++i];
|
|
62
|
+
if (v === undefined) fail(`Flag ${a} requires a value.`);
|
|
63
|
+
out[a.slice(2)] = v;
|
|
64
|
+
} else if (boolFlags.includes(a)) out[a.slice(2)] = true;
|
|
65
|
+
else if (a.startsWith('-') && a !== '-') fail(`Unknown flag: ${a}`);
|
|
66
|
+
else out._.push(a);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One catalog line: "id label [bridged: copilot → gpt-5] [needs sign-in]". Pure. */
|
|
72
|
+
export function formatModelLine(m) {
|
|
73
|
+
const bits = [m.id];
|
|
74
|
+
if (m.label && m.label !== m.id) bits.push(`(${m.label})`);
|
|
75
|
+
if (m.custom === 'plugin') bits.push(`plugin:${m.plugin}`);
|
|
76
|
+
else if (m.custom === 'policy') bits.push('policy');
|
|
77
|
+
else if (m.custom === 'global') bits.push('yours');
|
|
78
|
+
else if (!m.custom) bits.push('built-in');
|
|
79
|
+
if (m.bridged) bits.push(`bridged: ${m.bridged}${m.upstreamModel ? ` → ${m.upstreamModel}` : ''}${m.upstreamApi === 'openai-chat' ? ' (translated)' : ''}`);
|
|
80
|
+
else if (m.routed) bits.push('endpoint-routed');
|
|
81
|
+
if (m.needsSignIn) bits.push(`NEEDS ${m.signInReason === 'no_key' ? 'API KEY' : m.signInReason === 'terms' ? 'ACKNOWLEDGEMENT' : 'SIGN-IN'}`);
|
|
82
|
+
if (m.hidden) bits.push('hidden');
|
|
83
|
+
return bits.join(' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Provider state lines. Pure. */
|
|
87
|
+
export function formatProviders(p) {
|
|
88
|
+
const c = p.copilot || {};
|
|
89
|
+
const lines = [
|
|
90
|
+
`copilot ${c.connected ? `connected${c.login ? ` as @${c.login}` : ''}` : 'not connected'} account=${c.accountType || 'individual'} maxConcurrent=${c.maxConcurrent} terms=${c.termsCurrent ? `acknowledged ${String(c.acknowledgedTerms || '').slice(0, 10)}` : 'not acknowledged'}${c.tokenSource === 'env' ? ` token=${c.tokenRef}` : ''}`,
|
|
91
|
+
];
|
|
92
|
+
if (c.quota) {
|
|
93
|
+
lines.push(` premium requests: ${c.quota.unlimited ? 'unlimited' : `${c.quota.used ?? '?'} / ${c.quota.entitlement ?? '?'}`}${c.quota.resetDate ? ` (resets ${c.quota.resetDate})` : ''}`);
|
|
94
|
+
}
|
|
95
|
+
for (const name of ['openai', 'anthropic']) {
|
|
96
|
+
const k = p[name] || {};
|
|
97
|
+
lines.push(`${name.padEnd(11)} ${k.configured ? 'key set' : k.keySet ? 'key ${VAR} NOT SET' : 'no key'}${k.keySource === 'env' ? ` (${k.keyRef})` : ''} baseUrl=${k.baseUrl || '-'} maxConcurrent=${k.maxConcurrent}`);
|
|
98
|
+
}
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function confirm(msg, yes, { c }) {
|
|
103
|
+
if (yes) return true;
|
|
104
|
+
if (!process.stdin.isTTY) return false;
|
|
105
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
106
|
+
rl.on('SIGINT', () => { rl.close(); process.exit(130); });
|
|
107
|
+
try {
|
|
108
|
+
const a = await new Promise((r) => rl.question(c('cyan', `${msg} [y/N] `), r));
|
|
109
|
+
return /^y(es)?$/i.test(String(a).trim());
|
|
110
|
+
} finally {
|
|
111
|
+
rl.close();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const sleep = (ms) => new Promise((r) => { const t = setTimeout(r, ms); if (t.unref) t.unref(); });
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The `worca models` entry point.
|
|
119
|
+
* @param {string[]} argv tokens after `models`
|
|
120
|
+
* @param {{out:(s:string)=>void, c:(name:string,s:string)=>string, fail:(m:string)=>never, sleep?:Function}} io
|
|
121
|
+
* @returns {Promise<number>} exit code
|
|
122
|
+
*/
|
|
123
|
+
export async function cmdModels(argv, { out, c, fail, sleep: wait = sleep }) {
|
|
124
|
+
const args = modelsArgs(argv, ['--pick', '--base-url'], ['--accept-terms', '--all', '--yes', '-h', '--help'], fail);
|
|
125
|
+
const [verb, ...rest] = args._;
|
|
126
|
+
if (!verb || args.help || args.h || verb === 'help') { out(MODELS_HELP); return 0; }
|
|
127
|
+
|
|
128
|
+
if (verb === 'list') {
|
|
129
|
+
const models = await listModels('');
|
|
130
|
+
for (const m of models) out(formatModelLine(m));
|
|
131
|
+
if (!models.length) out('(empty catalog)');
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (verb === 'providers') {
|
|
136
|
+
for (const line of formatProviders(await providersState({ quota: true }))) out(line);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const provider = rest[0];
|
|
141
|
+
if (verb === 'login') {
|
|
142
|
+
if (provider !== 'copilot') return fail('only `worca models login copilot` is supported');
|
|
143
|
+
const state = await providersState();
|
|
144
|
+
if (!state.copilot.termsCurrent) {
|
|
145
|
+
out(COPILOT_TERMS_TEXT);
|
|
146
|
+
out('');
|
|
147
|
+
const ok = args['accept-terms'] || await confirm('I understand and want to continue', false, { c });
|
|
148
|
+
if (!ok) return fail('sign-in cancelled — pass --accept-terms to acknowledge non-interactively');
|
|
149
|
+
await acknowledgeTerms();
|
|
150
|
+
}
|
|
151
|
+
const flow = await beginCopilotLogin();
|
|
152
|
+
out(`Open ${c('bold', flow.verificationUri)} and enter the code ${c('bold', flow.userCode)}`);
|
|
153
|
+
out(c('dim', `Waiting for approval (code valid ${Math.round(flow.expiresIn / 60)} min)…`));
|
|
154
|
+
let interval = flow.interval;
|
|
155
|
+
const deadline = Date.now() + flow.expiresIn * 1000;
|
|
156
|
+
while (Date.now() < deadline) {
|
|
157
|
+
await wait(interval * 1000);
|
|
158
|
+
const r = await pollCopilotLogin(flow.deviceCode);
|
|
159
|
+
if (r.ok) { out(c('green', `Connected to GitHub Copilot${r.login ? ` as @${r.login}` : ''}.`)); return 0; }
|
|
160
|
+
if (r.error) return fail(r.error);
|
|
161
|
+
if (r.interval) interval = r.interval;
|
|
162
|
+
}
|
|
163
|
+
return fail('the device code expired — run the command again');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (verb === 'logout') {
|
|
167
|
+
if (provider !== 'copilot') return fail('only `worca models logout copilot` is supported');
|
|
168
|
+
await copilotLogout();
|
|
169
|
+
out('Signed out of GitHub Copilot.');
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (verb === 'import' && provider === 'openai') {
|
|
174
|
+
// §8.4 for a server you run: the endpoint is asked what it serves, and only a window it
|
|
175
|
+
// really serves becomes a prompt limit — the rest is printed for the user to judge.
|
|
176
|
+
let out0;
|
|
177
|
+
try { out0 = await endpointModelsForImport({ baseUrl: args['base-url'] || '' }); } catch (err) { return fail(err.message || String(err)); }
|
|
178
|
+
const fmtCtx = (m) => (m.servedContext ? `${Math.round(m.servedContext / 1000)}k` : m.trainedContext ? `?/${Math.round(m.trainedContext / 1000)}k` : '-');
|
|
179
|
+
let ids;
|
|
180
|
+
if (args.pick) ids = String(args.pick).split(',').map((x) => x.trim()).filter(Boolean);
|
|
181
|
+
else if (args.all) ids = out0.models.filter((m) => m.importable).map((m) => m.id);
|
|
182
|
+
else {
|
|
183
|
+
out(`${out0.serverLabel} at ${out0.baseUrl} (pass --all, or --pick id,id,…):`);
|
|
184
|
+
for (const m of out0.models) {
|
|
185
|
+
out(` ${m.id.padEnd(34)} ${fmtCtx(m).padStart(7)} ${m.toolCalls ? 'tools' : m.toolCalls === null ? 'tools?' : ' '} ${m.vision ? 'vision' : ' '}${m.loaded === true ? ' loaded' : ''}${m.inCatalog ? ' (in catalog)' : ''}${m.importable ? '' : ` (${m.blocked})`}`);
|
|
186
|
+
}
|
|
187
|
+
for (const w of out0.warnings) out(c('yellow', ` ! ${w}`));
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
for (const w of out0.warnings) out(c('yellow', `! ${w}`));
|
|
191
|
+
if (!(await confirm(`Import ${ids.length} model${ids.length === 1 ? '' : 's'} into the catalog?`, args.yes, { c }))) return fail('cancelled');
|
|
192
|
+
const r = await importEndpointModels(ids, { baseUrl: args['base-url'] || '' });
|
|
193
|
+
for (const id of r.created) out(` + ${id}`);
|
|
194
|
+
for (const id of r.updated) out(` ~ ${id} (upstream refreshed)`);
|
|
195
|
+
for (const sk of r.skipped) out(` - ${sk.id} (skipped: ${sk.why})`);
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (verb === 'import') {
|
|
200
|
+
if (provider !== 'copilot') return fail('only `worca models import copilot` and `worca models import openai` are supported');
|
|
201
|
+
let list;
|
|
202
|
+
try { list = await copilotModelsForImport(); } catch (err) {
|
|
203
|
+
return fail(err && err.code === 'NOT_SIGNED_IN' ? 'not signed in — run `worca models login copilot` first' : (err.message || String(err)));
|
|
204
|
+
}
|
|
205
|
+
const importable = list.filter((m) => !m.policyState || m.policyState === 'enabled');
|
|
206
|
+
let ids;
|
|
207
|
+
if (args.pick) ids = String(args.pick).split(',').map((s) => s.trim()).filter(Boolean);
|
|
208
|
+
else if (args.all) ids = importable.map((m) => m.id);
|
|
209
|
+
else {
|
|
210
|
+
out('Copilot models (pass --all, or --pick id,id,…):');
|
|
211
|
+
for (const m of list) out(` ${m.id.padEnd(28)} ${m.vendor.padEnd(10)} ${(m.contextWindow ? `${Math.round(m.contextWindow / 1000)}k` : '-').padStart(5)} ${m.toolCalls ? 'tools' : ' '} ${m.vision ? 'vision' : ' '} ${m.reasoning ? 'reasoning' : ' '}${m.inCatalog ? ' (in catalog)' : ''}${m.policyState && m.policyState !== 'enabled' ? ' (disabled in Copilot settings)' : ''}`);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
out('These run through your Copilot subscription. Claude models keep extended thinking; other vendors run through a translation layer (no thinking blocks, no web tools).');
|
|
215
|
+
if (!(await confirm(`Import ${ids.length} model${ids.length === 1 ? '' : 's'} into the catalog?`, args.yes, { c }))) return fail('cancelled');
|
|
216
|
+
const r = await importCopilotModels(ids);
|
|
217
|
+
for (const id of r.created) out(` + ${id}`);
|
|
218
|
+
for (const id of r.updated) out(` ~ ${id} (capabilities refreshed)`);
|
|
219
|
+
for (const id of r.skipped) out(` - ${id} (skipped: not offered, or not a copilot entry)`);
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (verb === 'test') {
|
|
224
|
+
if (!provider) return fail('usage: worca models test <copilot|openai|anthropic>');
|
|
225
|
+
const r = await testProviderConnection(provider);
|
|
226
|
+
if (r.ok) { out(c('green', `✓ ${provider} reachable${r.models != null ? ` — ${r.models} models listed` : ''}`)); return 0; }
|
|
227
|
+
out(c('red', `✗ ${provider}: ${r.message}`));
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (verb === 'set') {
|
|
232
|
+
if (!provider) return fail('usage: worca models set <provider> key=value …');
|
|
233
|
+
const patch = {};
|
|
234
|
+
for (const kv of rest.slice(1)) {
|
|
235
|
+
const i = kv.indexOf('=');
|
|
236
|
+
if (i <= 0) return fail(`expected key=value, got ${JSON.stringify(kv)}`);
|
|
237
|
+
const k = kv.slice(0, i); const v = kv.slice(i + 1);
|
|
238
|
+
patch[k] = k === 'maxConcurrent' ? Number(v) : v;
|
|
239
|
+
}
|
|
240
|
+
if (!Object.keys(patch).length) return fail('nothing to set');
|
|
241
|
+
try { await patchProvider(provider, patch); } catch (err) { return fail(err.message || String(err)); }
|
|
242
|
+
for (const line of formatProviders(await providersState())) out(line);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return fail(`unknown models subcommand ${JSON.stringify(verb)} — see \`worca models help\``);
|
|
247
|
+
}
|
package/src/cli/render.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// ■ End ← Reviewer.pass → plan-review.md End bound — End's ONLY line
|
|
16
16
|
// Flow nodes never print a start/paused/error line; `skipped` and P8's bookend
|
|
17
17
|
// executions render nothing; `token` events are never rendered.
|
|
18
|
-
import { BOOKEND_EXECUTION_IDS } from '../shared/graph/constants.mjs';
|
|
18
|
+
import { BOOKEND_EXECUTION_IDS, KEYED_KINDS } from '../shared/graph/constants.mjs';
|
|
19
19
|
|
|
20
20
|
const nodesOf = (m) => ((m && m.graph && m.graph.nodes) || []).filter(Boolean);
|
|
21
21
|
const wiresOf = (m) => ((m && m.graph && m.graph.wires) || []).filter(Boolean);
|
|
@@ -76,7 +76,7 @@ export function formatExecLine(ev, manifest, { color = (n, s) => s } = {}) {
|
|
|
76
76
|
const tail = r.path ? ` → ${base(r.path)}` : (r.value != null ? ` → ${String(r.value)}` : '');
|
|
77
77
|
return `${color('bold', '■')} ${label}${from}${tail}`;
|
|
78
78
|
}
|
|
79
|
-
if (node && node.kind
|
|
79
|
+
if (node && !KEYED_KINDS.includes(node.kind)) { // a flow card: one dim ✓ line, no ordinal / duration / cost
|
|
80
80
|
if (ev.status !== 'done') return '';
|
|
81
81
|
return `${color('green', '✓')} ${label}${MARKED_FLOW.has(node.kind) ? flowMarker(node, m, color) : ''}`;
|
|
82
82
|
}
|
|
@@ -86,7 +86,9 @@ export function formatExecLine(ev, manifest, { color = (n, s) => s } = {}) {
|
|
|
86
86
|
const dur = ev.durationMs != null ? ` ${fmtDur(ev.durationMs)}` : '';
|
|
87
87
|
if (ev.status === 'error') return `${color('red', '✗')} ${label}${ord}${dur} — ${ev.error || 'failed'}`;
|
|
88
88
|
if (ev.status !== 'done') return ''; // `skipped` (and anything unknown) renders nothing
|
|
89
|
-
const cost =
|
|
89
|
+
const cost = node && node.kind === 'script'
|
|
90
|
+
? (ev.exitCode != null ? ` · exit ${ev.exitCode}` : '')
|
|
91
|
+
: (ev.costUsd != null ? ` · ${usd(ev.costUsd)}` : '');
|
|
90
92
|
const verdict = ev.verdict ? (ev.verdict.missing ? ' — no verdict written (treated as clean)' : ev.verdict.hasBlocking ? ' — blocking' : ' — clean') : '';
|
|
91
93
|
return `${color('green', '✓')} ${label}${ord}${dur}${cost}${verdict}`;
|
|
92
94
|
}
|
|
@@ -169,7 +171,7 @@ export function formatWorkflowProposal(w) {
|
|
|
169
171
|
if (cues.length) lines.push(` ${cues.join(' · ')}`);
|
|
170
172
|
const nodes = p.manifest?.graph?.nodes || [];
|
|
171
173
|
const wires = p.manifest?.graph?.wires || [];
|
|
172
|
-
const agents = nodes.filter((n) => n.kind
|
|
174
|
+
const agents = nodes.filter((n) => KEYED_KINDS.includes(n.kind));
|
|
173
175
|
const ordered = Array.isArray(p.order) && p.order.length
|
|
174
176
|
? p.order.map((id) => agents.find((n) => n.id === id)).filter(Boolean)
|
|
175
177
|
: agents;
|
|
@@ -182,3 +184,69 @@ export function formatWorkflowProposal(w) {
|
|
|
182
184
|
if (Number(p.costUsd) > 0) lines.push(` classifier cost so far: $${Number(p.costUsd).toFixed(2)}`);
|
|
183
185
|
return lines;
|
|
184
186
|
}
|
|
187
|
+
|
|
188
|
+
// ── ask forms: the CLI's prompt FORMATTING (spec §8) ────────────────────────────
|
|
189
|
+
//
|
|
190
|
+
// The readline loop lives in worca-cc.mjs and every coercion rule lives in P1's
|
|
191
|
+
// coerceInput() — nothing here parses. `field` is one entry of
|
|
192
|
+
// promptFields(ask): { field, label, widget, type, schema, options: [{value,label}],
|
|
193
|
+
// items, itemFields, verdicts, free, default, required, when }. Colour is the
|
|
194
|
+
// caller's, exactly like formatExecLine.
|
|
195
|
+
|
|
196
|
+
/** How many times the CLI re-offers a form before giving up (MAX_QUESTION_ROUNDS's twin). */
|
|
197
|
+
export const FORM_REPROMPT_MAX = 3;
|
|
198
|
+
|
|
199
|
+
const isNum = (type) => type === 'number' || type === 'integer';
|
|
200
|
+
|
|
201
|
+
/** The `, Enter = <default>` / ` [Enter = <default>]` tail, or ''. */
|
|
202
|
+
function defaultHint(field, { bare = false } = {}) {
|
|
203
|
+
const d = field.default;
|
|
204
|
+
if (d === undefined || d === null || d === '') return '';
|
|
205
|
+
const text = Array.isArray(d) ? d.join(', ') : String(d);
|
|
206
|
+
return bare ? ` [Enter = ${text}]` : `, Enter = ${text}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The lines to print for ONE form field, plus its readline prompt. `prompt` is null
|
|
211
|
+
* for `review-list`: the caller loops field.items and prompts each field.itemFields
|
|
212
|
+
* entry itself.
|
|
213
|
+
* @param {object} field one promptFields() entry
|
|
214
|
+
* @returns {{lines: string[], prompt: string|null}}
|
|
215
|
+
*/
|
|
216
|
+
export function formatFormField(field) {
|
|
217
|
+
const f = field || {};
|
|
218
|
+
const label = f.label || f.field;
|
|
219
|
+
const lines = [`${label}${f.required ? ' *' : ''}`];
|
|
220
|
+
const choices = Array.isArray(f.options) ? f.options : [];
|
|
221
|
+
if (choices.length) choices.forEach((o, i) => lines.push(` ${i + 1}) ${String(o.label)}`));
|
|
222
|
+
|
|
223
|
+
if (f.widget === 'review-list') return { lines, prompt: null };
|
|
224
|
+
const hint = defaultHint(f);
|
|
225
|
+
if (f.widget === 'toggle' || f.type === 'boolean') return { lines, prompt: `Choose [y/n${hint}]: ` };
|
|
226
|
+
if (f.widget === 'rank') return { lines, prompt: `Order [comma-separated numbers or ids${hint}]: ` };
|
|
227
|
+
if (f.type === 'array') return { lines, prompt: `Choose [numbers or values, comma-separated${hint}]: ` };
|
|
228
|
+
// A `suggest` select (P1 C14) accepts free text beside its suggestions.
|
|
229
|
+
if (choices.length && f.free) return { lines, prompt: `Choose [number, value or your own text${hint}]: ` };
|
|
230
|
+
if (choices.length) return { lines, prompt: `Choose [number or value${hint}]: ` };
|
|
231
|
+
if (isNum(f.type)) return { lines, prompt: `Enter a number${defaultHint(f, { bare: true })}: ` };
|
|
232
|
+
if (f.widget === 'date') return { lines, prompt: `Enter a date (YYYY-MM-DD)${defaultHint(f, { bare: true })}: ` };
|
|
233
|
+
return { lines, prompt: `Your answer${defaultHint(f, { bare: true })}: ` };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A failed coerceInput() as ONE printable line. P1's message names the problem but not
|
|
237
|
+
* the field, because a chat reply prefixes the path instead (X7). */
|
|
238
|
+
export function formatCoerceError(field, res) {
|
|
239
|
+
const f = field || {};
|
|
240
|
+
const label = f.label || f.field || '';
|
|
241
|
+
const message = res && res.message ? String(res.message) : 'invalid value';
|
|
242
|
+
return label ? ` ${label}: ${message}` : ` ${message}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** P1 validate()/collectAnswer() errors as indented printable lines. */
|
|
246
|
+
export function formatFormErrors(errors) {
|
|
247
|
+
return (Array.isArray(errors) ? errors : []).map((e) => {
|
|
248
|
+
const path = e && e.path ? String(e.path) : '';
|
|
249
|
+
const message = e && e.message ? String(e.message) : 'invalid value';
|
|
250
|
+
return path ? ` ${path}: ${message}` : ` ${message}`;
|
|
251
|
+
});
|
|
252
|
+
}
|