@worca/app 1.6.0-rc.1 → 1.6.0-rc.2
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 +1 -1
- package/package.json +1 -1
- package/src/core/ask/turn.mjs +5 -1
- package/src/core/bridge/provider-ops.mjs +40 -4
- package/ui/public/app.js +27 -11
- package/ui/public/bridge-view.mjs +42 -4
- package/ui/public/index.html +277 -245
- package/ui/public/style.css +11 -4
package/README.md
CHANGED
|
@@ -234,7 +234,7 @@ See [`docs/team-policy.md`](docs/team-policy.md).
|
|
|
234
234
|
into that model's agent spawns. Share a model catalog as a plugin, with
|
|
235
235
|
secrets required at install time.
|
|
236
236
|
- **No first-party account needed** — run and chat titles are written by the
|
|
237
|
-
model the run or chat itself uses (Settings ›
|
|
237
|
+
model the run or chat itself uses (Settings › Models › Title generation picks
|
|
238
238
|
a fixed one instead), endpoint-routed models carry Claude Code's internal
|
|
239
239
|
haiku/sonnet/opus/fable tier keys so nothing falls back to the Anthropic API,
|
|
240
240
|
and *Hide built-in models* (Settings › Models) drops the built-ins from every
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worca/app",
|
|
3
|
-
"version": "1.6.0-rc.
|
|
3
|
+
"version": "1.6.0-rc.2",
|
|
4
4
|
"description": "Worca — deterministic multi-agent pipeline that drives Claude Code (headless) through Plan -> Refine -> Implement -> Review, with a CLI, an installable /worca skill, and a web UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sinisha Djukic",
|
package/src/core/ask/turn.mjs
CHANGED
|
@@ -658,7 +658,11 @@ class AskTurn extends EventEmitter {
|
|
|
658
658
|
await d.fs.writeFile(
|
|
659
659
|
mcpConfigPath,
|
|
660
660
|
JSON.stringify(d.buildMcpConfig({ homeBase, threadId: this.threadId, serverPath: d.serverPath, ...(this.reader ? { reader: this.reader } : {}), ...(this.relay ? { relay: this.relay } : {}), ...(this.web ? { web: this.web } : {}) }), null, 2),
|
|
661
|
-
|
|
661
|
+
// Never a key value (webKeyVar: the key rides the process env). A relayed turn runs as
|
|
662
|
+
// the person's agent user (agent-pool.mjs), which reads this file through its group: the
|
|
663
|
+
// scratch dir is setgid worca-share (2770), so 0640 reaches the agent users and nobody
|
|
664
|
+
// else. It carries this turn's relay token, which that agent must present anyway.
|
|
665
|
+
{ encoding: 'utf8', mode: this.relay ? 0o640 : 0o600 },
|
|
662
666
|
);
|
|
663
667
|
// One 30-minute budget for the whole turn, retry included. The timedOut
|
|
664
668
|
// flag and abort() run in ONE synchronous callback, so R-C always reads
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from './providers/copilot.mjs';
|
|
20
20
|
import { keyOptional } from './registry.mjs';
|
|
21
21
|
import { listEndpointModels, catalogEntryForEndpointModel, importableModel } from './providers/endpoint.mjs';
|
|
22
|
-
import { brokerEnabled, brokerInfo, cachedBrokerInfo, mintSpawnToken, revokeSpawnToken, slotBaseUrl } from '../broker-client.mjs';
|
|
22
|
+
import { brokerEnabled, brokerInfo, cachedBrokerInfo, mintSpawnToken, revokeSpawnToken, slotBaseUrl, personSlots } from '../broker-client.mjs';
|
|
23
23
|
import { routeUpstream } from '../broker-routing.mjs';
|
|
24
24
|
import { resolveBillTo } from '../billing.mjs';
|
|
25
25
|
|
|
@@ -129,7 +129,7 @@ const secretSource = (v) => (!v ? null : modelEnvRef(v) ? 'env' : 'stored');
|
|
|
129
129
|
* a network call to GitHub — and is null when the account exposes none.
|
|
130
130
|
* @param {{quota?:boolean, fetch?:typeof fetch}} [opts]
|
|
131
131
|
*/
|
|
132
|
-
export async function providersState({ quota = false, fetch: f } = {}) {
|
|
132
|
+
export async function providersState({ quota = false, fetch: f, person = resolveBillTo(null) } = {}) {
|
|
133
133
|
const all = allProviders();
|
|
134
134
|
const c = all.copilot;
|
|
135
135
|
const token = resolveProviderSecret(c.githubToken);
|
|
@@ -161,11 +161,47 @@ export async function providersState({ quota = false, fetch: f } = {}) {
|
|
|
161
161
|
maxConcurrent: p.maxConcurrent,
|
|
162
162
|
};
|
|
163
163
|
};
|
|
164
|
-
// With the credential broker on, keys and the Copilot sign-in are per person on its key page
|
|
165
|
-
|
|
164
|
+
// With the credential broker on, keys and the Copilot sign-in are per person on its key page:
|
|
165
|
+
// each provider's state is the viewer's own slot there, not worca's (empty) settings.
|
|
166
|
+
const broker = brokerEnabled() ? await brokerProviders(all, person) : { enabled: false };
|
|
166
167
|
return { copilot, openai: keyed('openai'), anthropic: keyed('anthropic'), broker };
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
/**
|
|
171
|
+
* The Providers card's view of the broker: which slot each provider spends from and the viewer's
|
|
172
|
+
* state in it — {slot, state:'set'|'missing'|'invalid'|'operator'|'keyless'|'none', suffix?, error?}.
|
|
173
|
+
* Never throws: an unreachable broker leaves the states out and says why.
|
|
174
|
+
*/
|
|
175
|
+
async function brokerProviders(all, person) {
|
|
176
|
+
const info = cachedBrokerInfo() || await brokerInfo().catch(() => null);
|
|
177
|
+
const out = { enabled: true, keyPage: info?.publicUrl || null, mode: info?.mode || null, providers: {} };
|
|
178
|
+
const slots = info?.slots || [];
|
|
179
|
+
const route = (name) => {
|
|
180
|
+
if (name === 'copilot') {
|
|
181
|
+
const s = slots.find((x) => x.auth === 'copilot');
|
|
182
|
+
return s ? { slot: s.id } : { state: 'none', error: 'the credential broker has no GitHub Copilot slot' };
|
|
183
|
+
}
|
|
184
|
+
const r = routeUpstream({ provider: name, baseUrl: all[name].baseUrl || null }, { slots });
|
|
185
|
+
return r.slot ? { slot: r.slot } : r.keyless ? { state: 'keyless' } : { state: 'none', error: r.error };
|
|
186
|
+
};
|
|
187
|
+
for (const name of ['copilot', 'openai', 'anthropic']) out.providers[name] = route(name);
|
|
188
|
+
const who = out.mode === 'multi' ? person : 'local';
|
|
189
|
+
if (!who || (out.mode === 'multi' && who === 'local')) { out.signInNeeded = true; return out; }
|
|
190
|
+
try {
|
|
191
|
+
const mine = new Map(((await personSlots(who)).slots || []).map((x) => [x.id, x]));
|
|
192
|
+
for (const p of Object.values(out.providers)) {
|
|
193
|
+
if (!p.slot) continue;
|
|
194
|
+
const s = mine.get(p.slot);
|
|
195
|
+
p.state = s ? s.state : 'missing';
|
|
196
|
+
if (s && s.suffix) p.suffix = s.suffix;
|
|
197
|
+
if (s && s.label) p.label = s.label;
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
out.error = err.message;
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
169
205
|
/** Patch a provider from the UI/CLI; masked key echoes are dropped ("keep"). */
|
|
170
206
|
export async function patchProvider(name, patch = {}) {
|
|
171
207
|
if (!UPSTREAM_PROVIDERS.includes(name)) throw new Error(`unknown provider ${JSON.stringify(name)}`);
|
package/ui/public/app.js
CHANGED
|
@@ -773,7 +773,7 @@ function paintBudget() {
|
|
|
773
773
|
topAmt.classList.toggle('over', !!b.blocked);
|
|
774
774
|
}
|
|
775
775
|
applyBudgetToNewView();
|
|
776
|
-
if (currentView() === 'settings') paintBudgetReadout();
|
|
776
|
+
if (currentView() === 'settings' && currentSettingsTab === 'runs') paintBudgetReadout();
|
|
777
777
|
repaintCostBanners();
|
|
778
778
|
}
|
|
779
779
|
|
|
@@ -1066,7 +1066,7 @@ function handleServerMessage(msg) {
|
|
|
1066
1066
|
if (currentView() === 'projects') paintProjectPolicyCells(true);
|
|
1067
1067
|
if (currentView() === 'workspaces') paintWsPolicyLines(true);
|
|
1068
1068
|
if (currentView() === 'team-policy' && !tpState.editing) loadTeamPolicyView();
|
|
1069
|
-
if (currentView() === 'settings') paintTeamCapsReadout(true);
|
|
1069
|
+
if (currentView() === 'settings' && currentSettingsTab === 'runs') paintTeamCapsReadout(true);
|
|
1070
1070
|
if (currentView() === 'settings' && currentSettingsTab === 'plugins') paintPluginsPolicy(true);
|
|
1071
1071
|
if (currentView() === 'new') schedulePolicyLine();
|
|
1072
1072
|
return;
|
|
@@ -10953,7 +10953,7 @@ try {
|
|
|
10953
10953
|
if (mq && typeof mq.addEventListener === 'function') mq.addEventListener('change', () => applyTheme(document.documentElement.dataset.theme));
|
|
10954
10954
|
} catch { /* no media queries here */ }
|
|
10955
10955
|
|
|
10956
|
-
// Settings ›
|
|
10956
|
+
// Settings › Runs › Scheduled runs: the defaults a new schedule inherits.
|
|
10957
10957
|
function setSchedDefaultsMsg(text, kind) { setHintMsg('schedDefaultsMsg', text, kind); }
|
|
10958
10958
|
function paintScheduleSettings(data) {
|
|
10959
10959
|
const d = data && data.schedule;
|
|
@@ -12702,6 +12702,7 @@ async function loadModelsView() {
|
|
|
12702
12702
|
const pdata = await safeJson(pres);
|
|
12703
12703
|
mvState.providers = pres.ok ? pdata : null;
|
|
12704
12704
|
renderModelsViewBody();
|
|
12705
|
+
void paintHelperModelCards();
|
|
12705
12706
|
// "+ Add model…" from a picker on another page: the dialog opens once the catalog is here.
|
|
12706
12707
|
if (mvState.openEditorOnLoad) { mvState.openEditorOnLoad = false; openModelEditorDialog(); }
|
|
12707
12708
|
// Copilot's models feed the editor's upstream-id datalist; fetched in the
|
|
@@ -12716,6 +12717,18 @@ async function loadModelsView() {
|
|
|
12716
12717
|
}
|
|
12717
12718
|
}
|
|
12718
12719
|
|
|
12720
|
+
// Settings › Models › Title generation + Auto workflow model: both pickers list the catalog, so they
|
|
12721
|
+
// repaint whenever it loads — a model added a moment ago is selectable without leaving the tab.
|
|
12722
|
+
async function paintHelperModelCards() {
|
|
12723
|
+
try {
|
|
12724
|
+
const res = await fetch('/api/settings');
|
|
12725
|
+
if (!res.ok) return;
|
|
12726
|
+
const data = await safeJson(res);
|
|
12727
|
+
await paintTitleModelSettings(data);
|
|
12728
|
+
await paintAutoModelSettings(data);
|
|
12729
|
+
} catch { /* the cards keep their last paint */ }
|
|
12730
|
+
}
|
|
12731
|
+
|
|
12719
12732
|
/** The Providers tab: the same card, on a page of its own (§8.1). */
|
|
12720
12733
|
async function loadProvidersView() {
|
|
12721
12734
|
if (!el.providersList) return;
|
|
@@ -13886,7 +13899,7 @@ if (runListEl) {
|
|
|
13886
13899
|
if (runId) confirmCostOverride(runId, overrideBtn);
|
|
13887
13900
|
return;
|
|
13888
13901
|
}
|
|
13889
|
-
if (e.target.closest && e.target.closest('.cb-settings')) { location.hash = 'settings'; return; }
|
|
13902
|
+
if (e.target.closest && e.target.closest('.cb-settings')) { location.hash = 'settings/runs'; return; }
|
|
13890
13903
|
// Team-cap banner (team-policy design board 9): continue past, or open the page.
|
|
13891
13904
|
const pastBtn = e.target.closest && e.target.closest('.cb-past-team-cap');
|
|
13892
13905
|
if (pastBtn) {
|
|
@@ -14575,7 +14588,7 @@ if (tpSection) tpSection.addEventListener('click', async (e) => {
|
|
|
14575
14588
|
await handlePolicyPluginClick(e);
|
|
14576
14589
|
});
|
|
14577
14590
|
|
|
14578
|
-
// Settings › Budget (board 7): each home's caps as a readout, the tightest as a chip on the labels.
|
|
14591
|
+
// Settings › Runs › Budget (board 7): each home's caps as a readout, the tightest as a chip on the labels.
|
|
14579
14592
|
async function paintTeamCapsReadout(force = false) {
|
|
14580
14593
|
if (!el.teamCapsReadout) return;
|
|
14581
14594
|
const data = await loadTpScopes({ force });
|
|
@@ -17377,7 +17390,7 @@ function paintHdBanners(screen, record, data) {
|
|
|
17377
17390
|
{ pauseReason, pauseDetail, pipelineId: record.id, totalCostUsd: st.totalCostUsd },
|
|
17378
17391
|
{ budget: budgetState.budget || {}, fmt: { usd: fmtUsd, usd4: fmtUsd4, duration: fmtDuration, estTitle } });
|
|
17379
17392
|
const settingsBtn = banner.querySelector('.cb-settings');
|
|
17380
|
-
if (settingsBtn) settingsBtn.addEventListener('click', () => { location.hash = 'settings'; });
|
|
17393
|
+
if (settingsBtn) settingsBtn.addEventListener('click', () => { location.hash = 'settings/runs'; });
|
|
17381
17394
|
const overrideBtn = banner.querySelector('.cb-override');
|
|
17382
17395
|
if (overrideBtn) {
|
|
17383
17396
|
overrideBtn.addEventListener('click', () => {
|
|
@@ -22537,7 +22550,7 @@ el.runDetail?.addEventListener('click', (e) => {
|
|
|
22537
22550
|
if (!r) return;
|
|
22538
22551
|
const override = e.target.closest && e.target.closest('.cb-override');
|
|
22539
22552
|
if (override) { confirmCostOverride(r.runId, override); return; } // async, fire-and-forget
|
|
22540
|
-
if (e.target.closest && e.target.closest('.cb-settings')) { location.hash = 'settings'; return; }
|
|
22553
|
+
if (e.target.closest && e.target.closest('.cb-settings')) { location.hash = 'settings/runs'; return; }
|
|
22541
22554
|
const past = e.target.closest && e.target.closest('.cb-past-team-cap');
|
|
22542
22555
|
if (past) { confirmPastTeamCap(r.runId, past); return; } // team-policy board 9
|
|
22543
22556
|
if (e.target.closest && e.target.closest('.cb-policy-open')) { location.hash = 'team-policy'; return; }
|
|
@@ -23654,11 +23667,11 @@ const VIEW_MIN_LEVEL = Object.freeze({
|
|
|
23654
23667
|
'team-metrics': 'expert', 'team-policy': 'expert', agents: 'expert', scripts: 'expert',
|
|
23655
23668
|
schedules: 'advanced',
|
|
23656
23669
|
});
|
|
23657
|
-
const SETTINGS_TAB_MIN_LEVEL = Object.freeze({ guardrails: 'advanced', plugins: 'advanced', memory: 'advanced', models: 'expert', providers: 'expert' });
|
|
23670
|
+
const SETTINGS_TAB_MIN_LEVEL = Object.freeze({ ask: 'advanced', guardrails: 'advanced', plugins: 'advanced', memory: 'advanced', models: 'expert', providers: 'expert' });
|
|
23658
23671
|
const VIEW_TITLES = Object.freeze({
|
|
23659
23672
|
stats: 'Statistics', composer: 'Workflow Composer', workspaces: 'Workspaces', 'workspace-create': 'Workspaces',
|
|
23660
23673
|
'agent-create': 'Create agent', 'team-metrics': 'Team metrics', 'team-policy': 'Team policy', agents: 'Agents', scripts: 'Scripts',
|
|
23661
|
-
guardrails: 'Guardrails', plugins: 'Plugins', memory: 'Memory', models: 'Models', providers: 'Providers',
|
|
23674
|
+
guardrails: 'Guardrails', plugins: 'Plugins', memory: 'Memory', models: 'Models', providers: 'Providers', ask: 'Ask Worca',
|
|
23662
23675
|
schedules: 'Schedules',
|
|
23663
23676
|
});
|
|
23664
23677
|
function pageMinLevel() {
|
|
@@ -23715,7 +23728,10 @@ document.addEventListener('worca:level', () => {
|
|
|
23715
23728
|
// The tab is the Settings view's hash param; a guardrail deep link nests its id
|
|
23716
23729
|
// behind it (#settings/guardrails/<id>). parseHash splits on the FIRST '/' only,
|
|
23717
23730
|
// so that is view 'settings', param 'guardrails/<id>' — no parseHash change.
|
|
23718
|
-
const SETTINGS_TABS = ['general', '
|
|
23731
|
+
const SETTINGS_TABS = ['general', 'runs', 'ask', 'guardrails', 'memory', 'plugins', 'models', 'providers'];
|
|
23732
|
+
// The tabs whose cards GET /api/settings paints (loadSettings paints every card, wherever it sits).
|
|
23733
|
+
// Models is not one: loadModelsView repaints its two helper-model cards with the catalog.
|
|
23734
|
+
const SETTINGS_FORM_TABS = ['general', 'runs', 'ask'];
|
|
23719
23735
|
const settingsPanes = $$('[data-view="settings"] .settings-pane');
|
|
23720
23736
|
// Old top-level hashes keep working. The hashchange listener DROPS any view it
|
|
23721
23737
|
// does not know, so without this map a bookmark or an old in-app link would
|
|
@@ -23965,7 +23981,7 @@ function showSettingsTab(param = '') {
|
|
|
23965
23981
|
// request, and re-entry refetches (which is what lets grvExitWizard's
|
|
23966
23982
|
// '#settings/guardrails/<id>' -> '#settings/guardrails' hop reset the wizard).
|
|
23967
23983
|
paintLevelBanner();
|
|
23968
|
-
if (tab
|
|
23984
|
+
if (SETTINGS_FORM_TABS.includes(tab)) loadSettings();
|
|
23969
23985
|
if (tab === 'guardrails') loadGuardrailsView(sub);
|
|
23970
23986
|
if (tab === 'models') loadModelsView(sub);
|
|
23971
23987
|
if (tab === 'providers') loadProvidersView();
|
|
@@ -106,11 +106,48 @@ export function needsSignInPill(m, { doc = globalThis.document } = {}) {
|
|
|
106
106
|
// ── Providers card (§8.1) ────────────────────────────────────────────────────
|
|
107
107
|
|
|
108
108
|
function providerStatePill(doc, c) {
|
|
109
|
-
if (!c.termsCurrent)
|
|
109
|
+
if (!c.termsCurrent) {
|
|
110
|
+
// What unblocks sign-in, in the "Read notice" button's own words — and it opens that notice.
|
|
111
|
+
const pill = h(doc, 'button', 'badge grey mv-cp-terms mv-cp-terms-pill',
|
|
112
|
+
c.acknowledgedTerms ? 'please re-read the updated notice' : 'please read the notice first');
|
|
113
|
+
pill.type = 'button';
|
|
114
|
+
pill.title = 'Sign-in is blocked until you acknowledge the GitHub Copilot notice. Click to read it.';
|
|
115
|
+
return pill;
|
|
116
|
+
}
|
|
110
117
|
if (!c.connected) return h(doc, 'span', 'badge grey', 'not connected');
|
|
111
118
|
return h(doc, 'span', 'badge green', `connected${c.login ? ` as @${c.login}` : ''}`);
|
|
112
119
|
}
|
|
113
120
|
|
|
121
|
+
/**
|
|
122
|
+
* With the credential broker: the viewer's own state in the slot `name` spends from (key page),
|
|
123
|
+
* not worca's settings, which hold no key. A missing key links to the key page.
|
|
124
|
+
*/
|
|
125
|
+
function brokerPill(doc, b, name) {
|
|
126
|
+
const p = (b.providers || {})[name] || {};
|
|
127
|
+
const what = name === 'copilot' ? 'sign-in' : 'key';
|
|
128
|
+
if (b.signInNeeded) return h(doc, 'span', 'badge grey', `sign in to see your ${what}`);
|
|
129
|
+
if (p.state === 'none') {
|
|
130
|
+
const x = h(doc, 'span', 'badge red', 'no broker slot for this URL');
|
|
131
|
+
x.title = p.error || 'The credential broker has no slot for this base URL.';
|
|
132
|
+
return x;
|
|
133
|
+
}
|
|
134
|
+
if (p.state === 'keyless') return h(doc, 'span', 'badge green', 'local — no key needed');
|
|
135
|
+
if (b.error) {
|
|
136
|
+
const x = h(doc, 'span', 'badge grey', 'key page unreachable');
|
|
137
|
+
x.title = b.error;
|
|
138
|
+
return x;
|
|
139
|
+
}
|
|
140
|
+
if (p.state === 'set') return h(doc, 'span', 'badge green', `your ${what}${p.suffix ? ` ••••${p.suffix}` : ''}`);
|
|
141
|
+
if (p.state === 'operator') return h(doc, 'span', 'badge green', 'team key');
|
|
142
|
+
const label = p.state === 'invalid' ? `your ${what} was rejected` : `add your ${what} on the key page`;
|
|
143
|
+
const cls = p.state === 'invalid' ? 'badge red' : 'badge grey';
|
|
144
|
+
if (!b.keyPage) return h(doc, 'span', cls, label);
|
|
145
|
+
const a = h(doc, 'a', `${cls} mv-broker-pill`, label);
|
|
146
|
+
a.href = b.keyPage; a.target = '_blank'; a.rel = 'noopener';
|
|
147
|
+
a.title = 'Opens the key page in a new tab. Reopen this tab afterwards to see the change.';
|
|
148
|
+
return a;
|
|
149
|
+
}
|
|
150
|
+
|
|
114
151
|
function quotaLine(q) {
|
|
115
152
|
if (!q) return '';
|
|
116
153
|
if (q.unlimited) return 'Premium requests: unlimited on this plan';
|
|
@@ -181,7 +218,9 @@ export function renderProvidersCard(providers, { doc = globalThis.document, sign
|
|
|
181
218
|
const cpMain = h(doc, 'div', 'mv-pv-main');
|
|
182
219
|
const cpHead = h(doc, 'div', 'mv-head');
|
|
183
220
|
cpHead.appendChild(h(doc, 'b', 'mv-name', 'GitHub Copilot'));
|
|
184
|
-
|
|
221
|
+
const brokered = !!(p.broker && p.broker.enabled);
|
|
222
|
+
// The notice gates Copilot models either way; past it, the broker's view of your sign-in.
|
|
223
|
+
cpHead.appendChild(brokered && c.termsCurrent ? brokerPill(doc, p.broker, 'copilot') : providerStatePill(doc, c));
|
|
185
224
|
cpMain.appendChild(cpHead);
|
|
186
225
|
cpMain.appendChild(h(doc, 'small', 'hint',
|
|
187
226
|
c.acknowledgedTerms
|
|
@@ -215,7 +254,6 @@ export function renderProvidersCard(providers, { doc = globalThis.document, sign
|
|
|
215
254
|
const cpBtns = h(doc, 'div', 'mv-pv-btns');
|
|
216
255
|
const imp = h(doc, 'button', 'btn-ghost mv-cp-fetch-models', 'Import models…');
|
|
217
256
|
// With the broker, the import runs with the viewer's own Copilot sign-in (on the key page).
|
|
218
|
-
const brokered = !!(p.broker && p.broker.enabled);
|
|
219
257
|
imp.type = 'button'; imp.disabled = !c.connected && !brokered;
|
|
220
258
|
if (imp.disabled) imp.title = 'Sign in first';
|
|
221
259
|
cpBtns.appendChild(imp);
|
|
@@ -245,7 +283,7 @@ export function renderProvidersCard(providers, { doc = globalThis.document, sign
|
|
|
245
283
|
const main = h(doc, 'div', 'mv-pv-main');
|
|
246
284
|
const rh = h(doc, 'div', 'mv-head');
|
|
247
285
|
rh.appendChild(h(doc, 'b', 'mv-name', PROVIDER_LABELS[name]));
|
|
248
|
-
rh.appendChild(k.configured ? h(doc, 'span', 'badge green', 'key set')
|
|
286
|
+
rh.appendChild(p.broker && p.broker.enabled ? brokerPill(doc, p.broker, name) : k.configured ? h(doc, 'span', 'badge green', 'key set')
|
|
249
287
|
: k.keySet ? h(doc, 'span', 'badge red', 'key ${VAR} not set')
|
|
250
288
|
: k.keyOptional ? h(doc, 'span', 'badge green', 'local — no key needed') : h(doc, 'span', 'badge grey', 'no key'));
|
|
251
289
|
main.appendChild(rh);
|
package/ui/public/index.html
CHANGED
|
@@ -1350,12 +1350,16 @@
|
|
|
1350
1350
|
literal routing attribute name, so never spell it out in a comment. -->
|
|
1351
1351
|
<section class="view hidden" data-view="settings">
|
|
1352
1352
|
<div class="seg settings-tabs" id="settings-tabs" role="group" aria-label="Settings section">
|
|
1353
|
+
<!-- Ordered by mode (Simple, Advanced, Expert) so each mode sees a gap-free prefix of the
|
|
1354
|
+
strip; Models sits next to Providers. -->
|
|
1353
1355
|
<button type="button" class="on" data-tab="general" data-min-level="simple" aria-pressed="true">General</button>
|
|
1356
|
+
<button type="button" data-tab="runs" data-min-level="simple" aria-pressed="false">Runs</button>
|
|
1357
|
+
<button type="button" data-tab="ask" data-min-level="advanced" aria-pressed="false">Ask Worca</button>
|
|
1354
1358
|
<button type="button" data-tab="guardrails" data-min-level="advanced" aria-pressed="false">Guardrails</button>
|
|
1359
|
+
<button type="button" data-tab="memory" data-min-level="advanced" aria-pressed="false">Memory</button>
|
|
1360
|
+
<button type="button" data-tab="plugins" data-min-level="advanced" aria-pressed="false">Plugins</button>
|
|
1355
1361
|
<button type="button" data-tab="models" data-min-level="expert" aria-pressed="false">Models</button>
|
|
1356
1362
|
<button type="button" data-tab="providers" data-min-level="expert" aria-pressed="false">Providers</button>
|
|
1357
|
-
<button type="button" data-tab="plugins" data-min-level="advanced" aria-pressed="false">Plugins</button>
|
|
1358
|
-
<button type="button" data-tab="memory" data-min-level="advanced" aria-pressed="false">Memory</button>
|
|
1359
1363
|
</div>
|
|
1360
1364
|
|
|
1361
1365
|
<section class="settings-pane" data-tab="general">
|
|
@@ -1452,6 +1456,97 @@
|
|
|
1452
1456
|
<small class="hint" id="settingsMsg"></small>
|
|
1453
1457
|
</section>
|
|
1454
1458
|
|
|
1459
|
+
<!-- Spawn-debug diagnostics: the stored side of WORCA_DEBUG_SPAWN.
|
|
1460
|
+
claude-runner.mjs reads the setting per spawn (UI server AND CLI);
|
|
1461
|
+
a non-empty env var overrides it. This card only flips the gate —
|
|
1462
|
+
the runner decides what is printable (routing keys readable,
|
|
1463
|
+
credentials as "<set, N chars>"). -->
|
|
1464
|
+
<section class="card settings-card" id="debug-spawn-settings-card" data-min-level="expert">
|
|
1465
|
+
<div class="card-head">
|
|
1466
|
+
<div class="label-row">
|
|
1467
|
+
<h2>Spawn diagnostics</h2>
|
|
1468
|
+
<button type="button" class="info-tip" aria-label="About spawn diagnostics">i<span class="tip-content hidden">
|
|
1469
|
+
Logs the exact claude binary, argv and routing env handed to every spawn, in the run
|
|
1470
|
+
log. Credentials never appear: routing keys (endpoint, wire model) print readable,
|
|
1471
|
+
every other value as <code><set, N chars></code>. Applies to runs started here
|
|
1472
|
+
and from the CLI, with no restart. A non-empty <code>WORCA_DEBUG_SPAWN</code> in the
|
|
1473
|
+
environment overrides this setting.
|
|
1474
|
+
</span></button>
|
|
1475
|
+
</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
<div class="field">
|
|
1478
|
+
<label class="check-row" for="debugSpawnEnabled"><input id="debugSpawnEnabled" type="checkbox" /> Log claude spawn details (binary, argv, routing env)</label>
|
|
1479
|
+
</div>
|
|
1480
|
+
<small class="hint" id="debugSpawnEnvNote"></small>
|
|
1481
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1482
|
+
<button type="button" id="debugSpawnReset" class="btn btn-ghost btn-mini">Reset to default</button>
|
|
1483
|
+
<button type="button" id="debugSpawnSave" class="btn btn-primary btn-mini">Save</button>
|
|
1484
|
+
</div>
|
|
1485
|
+
<small class="hint" id="debugSpawnMsg"></small>
|
|
1486
|
+
</section>
|
|
1487
|
+
|
|
1488
|
+
<!-- Getting started: the checklist's Hide is undone here. Only the checklist —
|
|
1489
|
+
the welcome dialog stays seen. No info-tip (the row explains itself);
|
|
1490
|
+
the .hint is painted by app.js like every other settings status line. -->
|
|
1491
|
+
<section class="card settings-card" id="getting-started-card" data-min-level="simple">
|
|
1492
|
+
<div class="label-row">
|
|
1493
|
+
<h2>Getting started</h2>
|
|
1494
|
+
</div>
|
|
1495
|
+
<div class="gs-settings-row">
|
|
1496
|
+
<p class="gs-settings-text">The checklist on New pipeline walks through the first nine things to do with Worca. Hidden it, or want the guided tour back?</p>
|
|
1497
|
+
<button type="button" id="gsShowAgain" class="btn btn-ghost btn-mini">Show again</button>
|
|
1498
|
+
</div>
|
|
1499
|
+
<small class="hint" id="gsSettingsMsg"></small>
|
|
1500
|
+
</section>
|
|
1501
|
+
|
|
1502
|
+
<!-- About: intentionally no info-tip and no .hint (test/ui-settings-tooltips.test.mjs pins both). -->
|
|
1503
|
+
<section class="card settings-card" id="about-card" data-min-level="simple">
|
|
1504
|
+
<div class="label-row">
|
|
1505
|
+
<h2>About</h2>
|
|
1506
|
+
</div>
|
|
1507
|
+
<div class="about-row">
|
|
1508
|
+
<span class="about-key">Version</span>
|
|
1509
|
+
<!-- href is painted from app.releaseUrl (the worca-app-v<version> tag);
|
|
1510
|
+
without one the anchor is plain text, so the placeholder never links. -->
|
|
1511
|
+
<a class="about-val about-version" id="aboutVersion"
|
|
1512
|
+
target="_blank" rel="noopener noreferrer">—</a>
|
|
1513
|
+
</div>
|
|
1514
|
+
<div class="about-row">
|
|
1515
|
+
<span class="about-key">Repository</span>
|
|
1516
|
+
<a class="about-val about-link" id="aboutRepoLink"
|
|
1517
|
+
href="https://github.com/SinishaDjukic/worca-cc"
|
|
1518
|
+
target="_blank" rel="noopener noreferrer">github.com/SinishaDjukic/worca-cc</a>
|
|
1519
|
+
</div>
|
|
1520
|
+
<div class="about-row">
|
|
1521
|
+
<span class="about-key">Feedback</span>
|
|
1522
|
+
<!-- hrefs are repainted from app.bugsUrl (package.json bugs.url); the
|
|
1523
|
+
static fallback keeps the links usable before /api/settings lands.
|
|
1524
|
+
Anchors only: ui-settings-about.test.mjs forbids a control here,
|
|
1525
|
+
and neither the label nor the href carries a version string. -->
|
|
1526
|
+
<a class="about-val about-link" id="aboutBugLink"
|
|
1527
|
+
href="https://github.com/SinishaDjukic/worca-cc/issues/new?labels=bug"
|
|
1528
|
+
target="_blank" rel="noopener noreferrer">Report a bug</a>
|
|
1529
|
+
</div>
|
|
1530
|
+
<div class="about-row">
|
|
1531
|
+
<span class="about-key"></span>
|
|
1532
|
+
<a class="about-val about-link" id="aboutIdeaLink"
|
|
1533
|
+
href="https://github.com/SinishaDjukic/worca-cc/issues/new?labels=enhancement"
|
|
1534
|
+
target="_blank" rel="noopener noreferrer">Suggest an improvement</a>
|
|
1535
|
+
</div>
|
|
1536
|
+
</section>
|
|
1537
|
+
</section>
|
|
1538
|
+
<!-- /pane general -->
|
|
1539
|
+
|
|
1540
|
+
<!-- ===== TAB: RUNS — how pipeline runs are capped, scheduled and reported.
|
|
1541
|
+
Simple, for Budget; the two Advanced cards keep their own level. ===== -->
|
|
1542
|
+
<section class="settings-pane hidden" data-tab="runs">
|
|
1543
|
+
<div class="topbar">
|
|
1544
|
+
<div>
|
|
1545
|
+
<h1>Runs</h1>
|
|
1546
|
+
<div class="sub">Cost limits and defaults for your pipeline runs</div>
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
|
|
1455
1550
|
<section class="card settings-card" id="budget-settings-card" data-min-level="simple">
|
|
1456
1551
|
<div class="card-head">
|
|
1457
1552
|
<div class="label-row">
|
|
@@ -1510,63 +1605,6 @@
|
|
|
1510
1605
|
<small class="hint" id="budgetMsg"></small>
|
|
1511
1606
|
</section>
|
|
1512
1607
|
|
|
1513
|
-
<!-- Ask Worca (ask-worca-design §6.9): per-turn guards for the
|
|
1514
|
-
assistant chat. Read fresh on every turn (D12); a stored null
|
|
1515
|
-
budget means "no cap". Posted ALONE — never with root/budget keys. -->
|
|
1516
|
-
<section class="card settings-card" id="ask-settings-card" data-min-level="advanced">
|
|
1517
|
-
<div class="card-head">
|
|
1518
|
-
<div class="label-row">
|
|
1519
|
-
<h2>Ask Worca</h2>
|
|
1520
|
-
<button type="button" class="info-tip" aria-label="About Ask Worca limits">i<span class="tip-content hidden">
|
|
1521
|
-
Per-turn guards for the Ask Worca assistant chat. A chat turn stops when it reaches the turn limit or the per-turn cost cap; both are read fresh on every turn.
|
|
1522
|
-
</span></button>
|
|
1523
|
-
</div>
|
|
1524
|
-
</div>
|
|
1525
|
-
<div class="field">
|
|
1526
|
-
<div class="label-row">
|
|
1527
|
-
<label for="askMaxTurns">Turn limit</label>
|
|
1528
|
-
<button type="button" class="info-tip" aria-label="About Turn limit">i<span class="tip-content hidden">
|
|
1529
|
-
Maximum assistant/tool turns one chat message may use (1–500). Leave empty to restore the default of 400.
|
|
1530
|
-
</span></button>
|
|
1531
|
-
</div>
|
|
1532
|
-
<input id="askMaxTurns" class="input" type="number" min="1" max="500" step="1" inputmode="numeric" placeholder="400" autocomplete="off" />
|
|
1533
|
-
</div>
|
|
1534
|
-
<div class="field">
|
|
1535
|
-
<div class="label-row">
|
|
1536
|
-
<label for="askMaxBudgetUsd">Per-turn cost cap (USD)</label>
|
|
1537
|
-
<button type="button" class="info-tip" aria-label="About Per-turn cost cap">i<span class="tip-content hidden">
|
|
1538
|
-
There is no cap by default. Untick No cap and enter an amount (0.1–100) to turn the guard on: a chat turn then stops when its estimated cost reaches it. Leaving the amount empty, or pressing Use defaults, means no cap.
|
|
1539
|
-
</span></button>
|
|
1540
|
-
</div>
|
|
1541
|
-
<input id="askMaxBudgetUsd" class="input" type="number" min="0.1" max="100" step="0.1" inputmode="decimal" placeholder="No cap" autocomplete="off" />
|
|
1542
|
-
<label class="check-row" for="askNoCap"><input id="askNoCap" type="checkbox" /> No cap</label>
|
|
1543
|
-
</div>
|
|
1544
|
-
<!-- Scripts workbench W20: the chat's save_script / test_script switch. Body
|
|
1545
|
-
rendered by chat-settings-view.mjs; saved with this card's Save button. -->
|
|
1546
|
-
<div class="field" id="ask-script-tools-host"></div>
|
|
1547
|
-
<!-- Ask web access (off by default): rendered by chat-settings-view.mjs; saved with this
|
|
1548
|
-
card's Save button, and only when one of its fields was touched. -->
|
|
1549
|
-
<div class="field" id="ask-web-host"></div>
|
|
1550
|
-
<div class="add-project-actions" style="margin-top:14px">
|
|
1551
|
-
<button type="button" id="askLimitsReset" class="btn btn-ghost btn-mini">Use defaults</button>
|
|
1552
|
-
<button type="button" id="askLimitsSave" class="btn btn-primary btn-mini">Save</button>
|
|
1553
|
-
</div>
|
|
1554
|
-
<small class="hint" id="askLimitsMsg"></small>
|
|
1555
|
-
<!-- Chat history: the counts paint with the view; the button opens a
|
|
1556
|
-
danger confirm quoting FRESH counts, then DELETE /api/ask/threads. -->
|
|
1557
|
-
<div class="field" style="margin-top:14px">
|
|
1558
|
-
<div class="label-row">
|
|
1559
|
-
<label for="askHistoryDelete">Chat history</label>
|
|
1560
|
-
<button type="button" class="info-tip" aria-label="About Chat history">i<span class="tip-content hidden">
|
|
1561
|
-
Every saved Ask Worca chat: transcripts, attachments and the git worktrees checked out for them. Runs started from a chat are not affected.
|
|
1562
|
-
</span></button>
|
|
1563
|
-
</div>
|
|
1564
|
-
<button type="button" id="askHistoryDelete" class="ask-history-delete" disabled>Delete all chat history</button>
|
|
1565
|
-
<small class="hint" id="askHistoryCounts"></small>
|
|
1566
|
-
<small class="hint" id="askHistoryMsg"></small>
|
|
1567
|
-
</div>
|
|
1568
|
-
</section>
|
|
1569
|
-
|
|
1570
1608
|
<!-- Scheduled runs: the defaults a NEW schedule inherits. Each schedule keeps
|
|
1571
1609
|
its own copy, so a change here never rewrites an existing one. -->
|
|
1572
1610
|
<section class="card settings-card" id="schedule-settings-card" data-min-level="advanced">
|
|
@@ -1618,95 +1656,6 @@
|
|
|
1618
1656
|
<small class="hint" id="schedDefaultsMsg"></small>
|
|
1619
1657
|
</section>
|
|
1620
1658
|
|
|
1621
|
-
<!-- Title generation (#422): which model writes run/chat titles.
|
|
1622
|
-
Empty = the model of the run or chat itself (title.mjs
|
|
1623
|
-
resolveTitleModel), so a first-party-free install needs no setup.
|
|
1624
|
-
The select is built from the project-less catalog; a stored id
|
|
1625
|
-
that left the catalog paints disabled + "not installed". -->
|
|
1626
|
-
<section class="card settings-card" id="title-model-settings-card" data-min-level="expert">
|
|
1627
|
-
<div class="card-head">
|
|
1628
|
-
<div class="label-row">
|
|
1629
|
-
<h2>Title generation</h2>
|
|
1630
|
-
<button type="button" class="info-tip" aria-label="About title generation">i<span class="tip-content hidden">
|
|
1631
|
-
Every run and chat gets a short LLM-written title. By default it is written by the
|
|
1632
|
-
model the run or chat itself uses, so an install without a first-party Anthropic
|
|
1633
|
-
account needs no setup here. Pick a model to write every title with it instead;
|
|
1634
|
-
the change applies to the next title, no restart. A non-empty
|
|
1635
|
-
<code>WORCA_TITLE_MODEL</code> in the environment overrides this setting.
|
|
1636
|
-
</span></button>
|
|
1637
|
-
</div>
|
|
1638
|
-
</div>
|
|
1639
|
-
<div class="field">
|
|
1640
|
-
<div class="label-row">
|
|
1641
|
-
<label for="titleModel">Model</label>
|
|
1642
|
-
</div>
|
|
1643
|
-
<select id="titleModel" class="select" aria-label="Title model"></select>
|
|
1644
|
-
</div>
|
|
1645
|
-
<small class="hint" id="titleModelEnvNote"></small>
|
|
1646
|
-
<div class="add-project-actions" style="margin-top:14px">
|
|
1647
|
-
<button type="button" id="titleModelTest" class="btn btn-ghost btn-mini" title="Send one tiny prompt to the selected model">Test</button>
|
|
1648
|
-
<button type="button" id="titleModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1649
|
-
<button type="button" id="titleModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1650
|
-
</div>
|
|
1651
|
-
<small class="hint" id="titleModelMsg"></small>
|
|
1652
|
-
</section>
|
|
1653
|
-
|
|
1654
|
-
<!-- Auto workflow model (spec D14 / §7.7): which model classifies a task into a workflow.
|
|
1655
|
-
Empty = the Sonnet-class default; WORCA_AUTO_MODEL in the environment wins. -->
|
|
1656
|
-
<section class="card settings-card" id="auto-model-settings-card" data-min-level="expert">
|
|
1657
|
-
<div class="card-head">
|
|
1658
|
-
<div class="label-row">
|
|
1659
|
-
<h2>Auto workflow model</h2>
|
|
1660
|
-
<button type="button" class="info-tip" aria-label="About the Auto workflow model">i<span class="tip-content hidden">
|
|
1661
|
-
When a run starts on <b>Auto</b>, one short model call reads the task and picks the workflow.
|
|
1662
|
-
Pick the model that makes that call; empty means the Sonnet-class default. A non-empty
|
|
1663
|
-
<code>WORCA_AUTO_MODEL</code> in the environment overrides this setting. Each call is billed to the
|
|
1664
|
-
run as a sub-agent row named "Auto workflow (round n)".
|
|
1665
|
-
</span></button>
|
|
1666
|
-
</div>
|
|
1667
|
-
</div>
|
|
1668
|
-
<div class="field">
|
|
1669
|
-
<div class="label-row"><label for="autoModel">Model</label></div>
|
|
1670
|
-
<select id="autoModel" class="select" aria-label="Auto workflow model"></select>
|
|
1671
|
-
</div>
|
|
1672
|
-
<small class="hint" id="autoModelEnvNote"></small>
|
|
1673
|
-
<div class="add-project-actions" style="margin-top:14px">
|
|
1674
|
-
<button type="button" id="autoModelTest" class="btn btn-ghost btn-mini" title="Send one tiny prompt to the selected model">Test</button>
|
|
1675
|
-
<button type="button" id="autoModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1676
|
-
<button type="button" id="autoModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1677
|
-
</div>
|
|
1678
|
-
<small class="hint" id="autoModelMsg"></small>
|
|
1679
|
-
</section>
|
|
1680
|
-
|
|
1681
|
-
<!-- Spawn-debug diagnostics: the stored side of WORCA_DEBUG_SPAWN.
|
|
1682
|
-
claude-runner.mjs reads the setting per spawn (UI server AND CLI);
|
|
1683
|
-
a non-empty env var overrides it. This card only flips the gate —
|
|
1684
|
-
the runner decides what is printable (routing keys readable,
|
|
1685
|
-
credentials as "<set, N chars>"). -->
|
|
1686
|
-
<section class="card settings-card" id="debug-spawn-settings-card" data-min-level="expert">
|
|
1687
|
-
<div class="card-head">
|
|
1688
|
-
<div class="label-row">
|
|
1689
|
-
<h2>Spawn diagnostics</h2>
|
|
1690
|
-
<button type="button" class="info-tip" aria-label="About spawn diagnostics">i<span class="tip-content hidden">
|
|
1691
|
-
Logs the exact claude binary, argv and routing env handed to every spawn, in the run
|
|
1692
|
-
log. Credentials never appear: routing keys (endpoint, wire model) print readable,
|
|
1693
|
-
every other value as <code><set, N chars></code>. Applies to runs started here
|
|
1694
|
-
and from the CLI, with no restart. A non-empty <code>WORCA_DEBUG_SPAWN</code> in the
|
|
1695
|
-
environment overrides this setting.
|
|
1696
|
-
</span></button>
|
|
1697
|
-
</div>
|
|
1698
|
-
</div>
|
|
1699
|
-
<div class="field">
|
|
1700
|
-
<label class="check-row" for="debugSpawnEnabled"><input id="debugSpawnEnabled" type="checkbox" /> Log claude spawn details (binary, argv, routing env)</label>
|
|
1701
|
-
</div>
|
|
1702
|
-
<small class="hint" id="debugSpawnEnvNote"></small>
|
|
1703
|
-
<div class="add-project-actions" style="margin-top:14px">
|
|
1704
|
-
<button type="button" id="debugSpawnReset" class="btn btn-ghost btn-mini">Reset to default</button>
|
|
1705
|
-
<button type="button" id="debugSpawnSave" class="btn btn-primary btn-mini">Save</button>
|
|
1706
|
-
</div>
|
|
1707
|
-
<small class="hint" id="debugSpawnMsg"></small>
|
|
1708
|
-
</section>
|
|
1709
|
-
|
|
1710
1659
|
<!-- Chat notifications (chat-connectivity-design.md §4.8): which run
|
|
1711
1660
|
events ping which chat channels. Body rendered by
|
|
1712
1661
|
chat-settings-view.mjs; app.js owns fetch/save/Test wiring. -->
|
|
@@ -1720,58 +1669,78 @@
|
|
|
1720
1669
|
</div>
|
|
1721
1670
|
<small class="hint" id="chatSettingsMsg"></small>
|
|
1722
1671
|
</section>
|
|
1672
|
+
</section>
|
|
1673
|
+
<!-- /pane runs -->
|
|
1723
1674
|
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
<
|
|
1730
|
-
</div>
|
|
1731
|
-
<div class="gs-settings-row">
|
|
1732
|
-
<p class="gs-settings-text">The checklist on New pipeline walks through the first nine things to do with Worca. Hidden it, or want the guided tour back?</p>
|
|
1733
|
-
<button type="button" id="gsShowAgain" class="btn btn-ghost btn-mini">Show again</button>
|
|
1675
|
+
<!-- ===== TAB: ASK WORCA — the assistant chat's limits, tools, web access and history ===== -->
|
|
1676
|
+
<section class="settings-pane hidden" data-tab="ask">
|
|
1677
|
+
<div class="topbar">
|
|
1678
|
+
<div>
|
|
1679
|
+
<h1>Ask Worca</h1>
|
|
1680
|
+
<div class="sub">The assistant chat: how far one turn may go, which tools and websites it may use, and its saved history</div>
|
|
1734
1681
|
</div>
|
|
1735
|
-
|
|
1736
|
-
</section>
|
|
1682
|
+
</div>
|
|
1737
1683
|
|
|
1738
|
-
<!--
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1684
|
+
<!-- Ask Worca (ask-worca-design §6.9): per-turn guards for the
|
|
1685
|
+
assistant chat. Read fresh on every turn (D12); a stored null
|
|
1686
|
+
budget means "no cap". Posted ALONE — never with root/budget keys. -->
|
|
1687
|
+
<!-- Simple on purpose: the Ask Worca tab (Advanced) already gates it, and a deep link at a lower mode
|
|
1688
|
+
must still show the card under the level banner (docs/ui-levels.md rule 3). -->
|
|
1689
|
+
<section class="card settings-card" id="ask-settings-card" data-min-level="simple">
|
|
1690
|
+
<div class="card-head">
|
|
1691
|
+
<div class="label-row">
|
|
1692
|
+
<h2>Limits & access</h2>
|
|
1693
|
+
<button type="button" class="info-tip" aria-label="About Ask Worca limits">i<span class="tip-content hidden">
|
|
1694
|
+
Per-turn guards for the Ask Worca assistant chat. A chat turn stops when it reaches the turn limit or the per-turn cost cap; both are read fresh on every turn.
|
|
1695
|
+
</span></button>
|
|
1696
|
+
</div>
|
|
1742
1697
|
</div>
|
|
1743
|
-
<div class="
|
|
1744
|
-
<
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1698
|
+
<div class="field">
|
|
1699
|
+
<div class="label-row">
|
|
1700
|
+
<label for="askMaxTurns">Turn limit</label>
|
|
1701
|
+
<button type="button" class="info-tip" aria-label="About Turn limit">i<span class="tip-content hidden">
|
|
1702
|
+
Maximum assistant/tool turns one chat message may use (1–500). Leave empty to restore the default of 400.
|
|
1703
|
+
</span></button>
|
|
1704
|
+
</div>
|
|
1705
|
+
<input id="askMaxTurns" class="input" type="number" min="1" max="500" step="1" inputmode="numeric" placeholder="400" autocomplete="off" />
|
|
1749
1706
|
</div>
|
|
1750
|
-
<div class="
|
|
1751
|
-
<
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1707
|
+
<div class="field">
|
|
1708
|
+
<div class="label-row">
|
|
1709
|
+
<label for="askMaxBudgetUsd">Per-turn cost cap (USD)</label>
|
|
1710
|
+
<button type="button" class="info-tip" aria-label="About Per-turn cost cap">i<span class="tip-content hidden">
|
|
1711
|
+
There is no cap by default. Untick No cap and enter an amount (0.1–100) to turn the guard on: a chat turn then stops when its estimated cost reaches it. Leaving the amount empty, or pressing Use defaults, means no cap.
|
|
1712
|
+
</span></button>
|
|
1713
|
+
</div>
|
|
1714
|
+
<input id="askMaxBudgetUsd" class="input" type="number" min="0.1" max="100" step="0.1" inputmode="decimal" placeholder="No cap" autocomplete="off" />
|
|
1715
|
+
<label class="check-row" for="askNoCap"><input id="askNoCap" type="checkbox" /> No cap</label>
|
|
1755
1716
|
</div>
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1717
|
+
<!-- Scripts workbench W20: the chat's save_script / test_script switch. Body
|
|
1718
|
+
rendered by chat-settings-view.mjs; saved with this card's Save button. -->
|
|
1719
|
+
<div class="field" id="ask-script-tools-host"></div>
|
|
1720
|
+
<!-- Ask web access (off by default): rendered by chat-settings-view.mjs; saved with this
|
|
1721
|
+
card's Save button, and only when one of its fields was touched. -->
|
|
1722
|
+
<div class="field" id="ask-web-host"></div>
|
|
1723
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1724
|
+
<button type="button" id="askLimitsReset" class="btn btn-ghost btn-mini">Use defaults</button>
|
|
1725
|
+
<button type="button" id="askLimitsSave" class="btn btn-primary btn-mini">Save</button>
|
|
1765
1726
|
</div>
|
|
1766
|
-
<
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1727
|
+
<small class="hint" id="askLimitsMsg"></small>
|
|
1728
|
+
<!-- Chat history: the counts paint with the view; the button opens a
|
|
1729
|
+
danger confirm quoting FRESH counts, then DELETE /api/ask/threads. -->
|
|
1730
|
+
<div class="field" style="margin-top:14px">
|
|
1731
|
+
<div class="label-row">
|
|
1732
|
+
<label for="askHistoryDelete">Chat history</label>
|
|
1733
|
+
<button type="button" class="info-tip" aria-label="About Chat history">i<span class="tip-content hidden">
|
|
1734
|
+
Every saved Ask Worca chat: transcripts, attachments and the git worktrees checked out for them. Runs started from a chat are not affected.
|
|
1735
|
+
</span></button>
|
|
1736
|
+
</div>
|
|
1737
|
+
<button type="button" id="askHistoryDelete" class="ask-history-delete" disabled>Delete all chat history</button>
|
|
1738
|
+
<small class="hint" id="askHistoryCounts"></small>
|
|
1739
|
+
<small class="hint" id="askHistoryMsg"></small>
|
|
1771
1740
|
</div>
|
|
1772
1741
|
</section>
|
|
1773
1742
|
</section>
|
|
1774
|
-
<!-- /pane
|
|
1743
|
+
<!-- /pane ask -->
|
|
1775
1744
|
|
|
1776
1745
|
<!-- ===== TAB: GUARDRAILS (was the top-level Guardrails view) ===== -->
|
|
1777
1746
|
<section class="settings-pane hidden" data-tab="guardrails">
|
|
@@ -1790,41 +1759,42 @@
|
|
|
1790
1759
|
</section>
|
|
1791
1760
|
<!-- /pane guardrails -->
|
|
1792
1761
|
|
|
1793
|
-
<!-- ===== TAB:
|
|
1794
|
-
<section class="settings-pane hidden" data-tab="
|
|
1762
|
+
<!-- ===== TAB: MEMORY (agent-memory-design.md §10) ===== -->
|
|
1763
|
+
<section class="settings-pane hidden" data-tab="memory">
|
|
1795
1764
|
<div class="topbar">
|
|
1796
1765
|
<div>
|
|
1797
|
-
<h1>
|
|
1798
|
-
<div class="sub">
|
|
1799
|
-
</div>
|
|
1800
|
-
<div style="display:flex;gap:8px;flex:0 0 auto">
|
|
1801
|
-
<button type="button" id="model-share-btn" class="btn-ghost" style="padding:11px 20px;white-space:nowrap">Share as plugin…</button>
|
|
1802
|
-
<button type="button" id="model-import-btn" class="btn-ghost" style="padding:11px 20px;white-space:nowrap">Import models…</button>
|
|
1803
|
-
<button type="button" id="model-create-btn" class="btn-go" style="padding:11px 20px;white-space:nowrap">
|
|
1804
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 5v14M5 12h14"></path></svg>
|
|
1805
|
-
Add model
|
|
1806
|
-
</button>
|
|
1766
|
+
<h1>Memory</h1>
|
|
1767
|
+
<div class="sub">What worca remembers across runs and chats — the global rules and preferences every agent sees. A project's own memory lives on its project page (Projects → open a project → Memory).</div>
|
|
1807
1768
|
</div>
|
|
1808
1769
|
</div>
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
<div class="
|
|
1770
|
+
<!-- Settings › Memory: the model + effort EVERY Memory defragment run uses (memory-defrag-model.mjs).
|
|
1771
|
+
Expert, like the health card's Defragment. No ⓘ and empty hints: the settings-view census
|
|
1772
|
+
(test/ui-settings-tooltips.test.mjs) pins both. A plain .card like the health card, NOT a
|
|
1773
|
+
.settings-card: that census (About last) counts the Settings cards. -->
|
|
1774
|
+
<section class="card mem-model-card" id="mem-defrag-model-card" data-min-level="expert">
|
|
1775
|
+
<div class="label-row"><h2>Defragment model</h2></div>
|
|
1776
|
+
<p class="mem-model-text">The model every Memory defragment run uses — from the Defragment button, New pipeline, an Ask Worca card or the CLI. A model named when the run starts (<code>worca --model</code>) still wins.</p>
|
|
1777
|
+
<div class="mem-model-row">
|
|
1778
|
+
<div class="field">
|
|
1779
|
+
<div class="label-row"><label for="memDefragModel">Model</label></div>
|
|
1780
|
+
<select id="memDefragModel" class="select" aria-label="Defragment model"></select>
|
|
1781
|
+
</div>
|
|
1782
|
+
<div class="field">
|
|
1783
|
+
<div class="label-row"><label for="memDefragEffort">Effort</label></div>
|
|
1784
|
+
<select id="memDefragEffort" class="select" aria-label="Defragment effort"></select>
|
|
1785
|
+
</div>
|
|
1822
1786
|
</div>
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1787
|
+
<small class="hint" id="memDefragModelNote"></small>
|
|
1788
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1789
|
+
<button type="button" id="memDefragModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1790
|
+
<button type="button" id="memDefragModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1791
|
+
</div>
|
|
1792
|
+
<small class="hint" id="memDefragModelMsg"></small>
|
|
1793
|
+
</section>
|
|
1794
|
+
<p id="memory-msg" class="form-msg" aria-live="polite"></p>
|
|
1795
|
+
<div id="memory-host" class="mem-host"></div>
|
|
1826
1796
|
</section>
|
|
1827
|
-
<!-- /pane
|
|
1797
|
+
<!-- /pane memory -->
|
|
1828
1798
|
|
|
1829
1799
|
<!-- ===== TAB: PLUGINS (was the top-level Plugins view) ===== -->
|
|
1830
1800
|
<section class="settings-pane hidden" data-tab="plugins">
|
|
@@ -1856,42 +1826,104 @@
|
|
|
1856
1826
|
</section>
|
|
1857
1827
|
<!-- /pane plugins -->
|
|
1858
1828
|
|
|
1859
|
-
<!-- ===== TAB:
|
|
1860
|
-
<section class="settings-pane hidden" data-tab="
|
|
1829
|
+
<!-- ===== TAB: MODELS (was the top-level Models view) ===== -->
|
|
1830
|
+
<section class="settings-pane hidden" data-tab="models">
|
|
1861
1831
|
<div class="topbar">
|
|
1862
1832
|
<div>
|
|
1863
|
-
<h1>
|
|
1864
|
-
<div class="sub">
|
|
1833
|
+
<h1>Models</h1>
|
|
1834
|
+
<div class="sub">The global model catalog — available in every project; entries can override built-ins, route through a provider, or carry per-model routing env. The two cards on top pick the models Worca's own titles and Auto classifier use. Sign-ins and endpoints live on the Providers tab.</div>
|
|
1835
|
+
</div>
|
|
1836
|
+
<div style="display:flex;gap:8px;flex:0 0 auto">
|
|
1837
|
+
<button type="button" id="model-share-btn" class="btn-ghost" style="padding:11px 20px;white-space:nowrap">Share as plugin…</button>
|
|
1838
|
+
<button type="button" id="model-import-btn" class="btn-ghost" style="padding:11px 20px;white-space:nowrap">Import models…</button>
|
|
1839
|
+
<button type="button" id="model-create-btn" class="btn-go" style="padding:11px 20px;white-space:nowrap">
|
|
1840
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 5v14M5 12h14"></path></svg>
|
|
1841
|
+
Add model
|
|
1842
|
+
</button>
|
|
1865
1843
|
</div>
|
|
1866
1844
|
</div>
|
|
1867
|
-
<!--
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1845
|
+
<!-- The models Worca's own helper calls use (titles, the Auto classifier). Two columns on a
|
|
1846
|
+
wide screen so the catalog below is not pushed a screen down. -->
|
|
1847
|
+
<div class="models-helpers" data-min-level="expert">
|
|
1848
|
+
<!-- Title generation (#422): which model writes run/chat titles.
|
|
1849
|
+
Empty = the model of the run or chat itself (title.mjs
|
|
1850
|
+
resolveTitleModel), so a first-party-free install needs no setup.
|
|
1851
|
+
The select is built from the project-less catalog; a stored id
|
|
1852
|
+
that left the catalog paints disabled + "not installed". -->
|
|
1853
|
+
<section class="card settings-card" id="title-model-settings-card" data-min-level="expert">
|
|
1854
|
+
<div class="card-head">
|
|
1855
|
+
<div class="label-row">
|
|
1856
|
+
<h2>Title generation</h2>
|
|
1857
|
+
<button type="button" class="info-tip" aria-label="About title generation">i<span class="tip-content hidden">
|
|
1858
|
+
Every run and chat gets a short LLM-written title. By default it is written by the
|
|
1859
|
+
model the run or chat itself uses, so an install without a first-party Anthropic
|
|
1860
|
+
account needs no setup here. Pick a model to write every title with it instead;
|
|
1861
|
+
the change applies to the next title, no restart. A non-empty
|
|
1862
|
+
<code>WORCA_TITLE_MODEL</code> in the environment overrides this setting.
|
|
1863
|
+
</span></button>
|
|
1864
|
+
</div>
|
|
1865
|
+
</div>
|
|
1875
1866
|
<div class="field">
|
|
1876
|
-
<div class="label-row"
|
|
1877
|
-
|
|
1867
|
+
<div class="label-row">
|
|
1868
|
+
<label for="titleModel">Model</label>
|
|
1869
|
+
</div>
|
|
1870
|
+
<select id="titleModel" class="select" aria-label="Title model"></select>
|
|
1871
|
+
</div>
|
|
1872
|
+
<small class="hint" id="titleModelEnvNote"></small>
|
|
1873
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1874
|
+
<button type="button" id="titleModelTest" class="btn btn-ghost btn-mini" title="Send one tiny prompt to the selected model">Test</button>
|
|
1875
|
+
<button type="button" id="titleModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1876
|
+
<button type="button" id="titleModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1877
|
+
</div>
|
|
1878
|
+
<small class="hint" id="titleModelMsg"></small>
|
|
1879
|
+
</section>
|
|
1880
|
+
|
|
1881
|
+
<!-- Auto workflow model (spec D14 / §7.7): which model classifies a task into a workflow.
|
|
1882
|
+
Empty = the Sonnet-class default; WORCA_AUTO_MODEL in the environment wins. -->
|
|
1883
|
+
<section class="card settings-card" id="auto-model-settings-card" data-min-level="expert">
|
|
1884
|
+
<div class="card-head">
|
|
1885
|
+
<div class="label-row">
|
|
1886
|
+
<h2>Auto workflow model</h2>
|
|
1887
|
+
<button type="button" class="info-tip" aria-label="About the Auto workflow model">i<span class="tip-content hidden">
|
|
1888
|
+
When a run starts on <b>Auto</b>, one short model call reads the task and picks the workflow.
|
|
1889
|
+
Pick the model that makes that call; empty means the Sonnet-class default. A non-empty
|
|
1890
|
+
<code>WORCA_AUTO_MODEL</code> in the environment overrides this setting. Each call is billed to the
|
|
1891
|
+
run as a sub-agent row named "Auto workflow (round n)".
|
|
1892
|
+
</span></button>
|
|
1893
|
+
</div>
|
|
1878
1894
|
</div>
|
|
1879
1895
|
<div class="field">
|
|
1880
|
-
<div class="label-row"><label for="
|
|
1881
|
-
<select id="
|
|
1896
|
+
<div class="label-row"><label for="autoModel">Model</label></div>
|
|
1897
|
+
<select id="autoModel" class="select" aria-label="Auto workflow model"></select>
|
|
1882
1898
|
</div>
|
|
1899
|
+
<small class="hint" id="autoModelEnvNote"></small>
|
|
1900
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1901
|
+
<button type="button" id="autoModelTest" class="btn btn-ghost btn-mini" title="Send one tiny prompt to the selected model">Test</button>
|
|
1902
|
+
<button type="button" id="autoModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1903
|
+
<button type="button" id="autoModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1904
|
+
</div>
|
|
1905
|
+
<small class="hint" id="autoModelMsg"></small>
|
|
1906
|
+
</section>
|
|
1907
|
+
</div>
|
|
1908
|
+
<p id="models-msg" class="form-msg" aria-live="polite"></p>
|
|
1909
|
+
<div class="run-list" id="models-list"></div>
|
|
1910
|
+
</section>
|
|
1911
|
+
<!-- /pane models -->
|
|
1912
|
+
|
|
1913
|
+
<!-- ===== TAB: PROVIDERS (model-bridge-design.md §8.1) =====
|
|
1914
|
+
Split out of Models: the card is account-level setup you touch rarely, and at
|
|
1915
|
+
1000+px it buried the catalog it unlocks. Expert only, like Models. -->
|
|
1916
|
+
<section class="settings-pane hidden" data-tab="providers">
|
|
1917
|
+
<div class="topbar">
|
|
1918
|
+
<div>
|
|
1919
|
+
<h1>Providers</h1>
|
|
1920
|
+
<div class="sub">Sign-ins, endpoints and keys for the models Worca bridges — GitHub Copilot, any OpenAI-compatible server (llama.cpp, Ollama, LM Studio, vLLM), an Anthropic-compatible gateway. The models themselves live on the Models tab.</div>
|
|
1883
1921
|
</div>
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
<button type="button" id="memDefragModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1888
|
-
</div>
|
|
1889
|
-
<small class="hint" id="memDefragModelMsg"></small>
|
|
1890
|
-
</section>
|
|
1891
|
-
<p id="memory-msg" class="form-msg" aria-live="polite"></p>
|
|
1892
|
-
<div id="memory-host" class="mem-host"></div>
|
|
1922
|
+
</div>
|
|
1923
|
+
<p id="providers-msg" class="form-msg" aria-live="polite"></p>
|
|
1924
|
+
<div class="run-list" id="providers-list"></div>
|
|
1893
1925
|
</section>
|
|
1894
|
-
<!-- /pane
|
|
1926
|
+
<!-- /pane providers -->
|
|
1895
1927
|
</section>
|
|
1896
1928
|
<!-- /view settings -->
|
|
1897
1929
|
</main>
|
package/ui/public/style.css
CHANGED
|
@@ -2357,6 +2357,13 @@ section[data-view="team-metrics"] .topbar{align-items:flex-start;}
|
|
|
2357
2357
|
/* Actor and Started never wrap: with the Saved column in, Title (ellipsis) and Workflow give way first. */
|
|
2358
2358
|
.tm-run-tbl td:nth-last-child(-n+2){white-space:nowrap;}
|
|
2359
2359
|
.badge.grey{background:var(--field);color:var(--ink-2);}
|
|
2360
|
+
/* The Copilot card's "read the notice first" pill is a button: it opens the notice (bridge-view.mjs). */
|
|
2361
|
+
.mv-cp-terms-pill{border:0;font-family:inherit;cursor:pointer;}
|
|
2362
|
+
.mv-cp-terms-pill:hover{background:var(--line);color:var(--ink);}
|
|
2363
|
+
.mv-cp-terms-pill:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
|
|
2364
|
+
/* With the credential broker, a provider pill for a missing key links to the key page. */
|
|
2365
|
+
.mv-broker-pill{text-decoration:none;cursor:pointer;}
|
|
2366
|
+
.mv-broker-pill:hover{text-decoration:underline;}
|
|
2360
2367
|
.tm-empty{display:grid;grid-template-columns:1fr 1fr;gap:14px;} .tm-empty .card{padding:22px;}
|
|
2361
2368
|
.tm-step{font-size:11px;letter-spacing:.08em;font-weight:700;color:var(--ink-2);}
|
|
2362
2369
|
.tm-empty-foot{display:block;margin-top:14px;}
|
|
@@ -2622,6 +2629,10 @@ button.tl-tile[aria-pressed="true"]{border-color:var(--ink);}
|
|
|
2622
2629
|
.settings-card{max-width:760px;padding:22px;margin-left:auto;margin-right:auto;}
|
|
2623
2630
|
.settings-card + .settings-card{margin-top:18px;}
|
|
2624
2631
|
.settings-card[hidden]{display:none;}
|
|
2632
|
+
/* Settings › Models: Title generation + Auto workflow model side by side above the catalog, so two
|
|
2633
|
+
short cards do not push it a screen down; one column once they would squeeze. */
|
|
2634
|
+
.models-helpers{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:18px;margin-bottom:18px;}
|
|
2635
|
+
.models-helpers > .settings-card{max-width:none;margin:0;}
|
|
2625
2636
|
|
|
2626
2637
|
/* My model credentials (credential broker): one row per slot, a state chip on the right. */
|
|
2627
2638
|
.cred-list{list-style:none;margin:0;padding:0;display:grid;gap:8px;}
|
|
@@ -5537,10 +5548,6 @@ a.ask-card-task-title:hover{text-decoration-color:currentColor;}
|
|
|
5537
5548
|
:root[data-level="expert"] [data-max-level="simple"],
|
|
5538
5549
|
:root[data-level="expert"] [data-max-level="advanced"]{display:none !important;}
|
|
5539
5550
|
|
|
5540
|
-
/* Simple shows General only: a one-button segmented control is noise, so the bar goes — unless a
|
|
5541
|
-
deep link parked us on a higher tab, whose button is then kept (paintLevelBanner). */
|
|
5542
|
-
:root[data-level="simple"] #settings-tabs:not(:has([data-level-keep])){display:none !important;}
|
|
5543
|
-
|
|
5544
5551
|
/* The icon: a stack of layers, unlit ones ghosted. currentColor, so hover, the
|
|
5545
5552
|
dark active fill and both themes come for free. */
|
|
5546
5553
|
/* An <i>, not a <span>: the collapsed rail visually hides every direct span child of a nav button
|