@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
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
// listener, routing on `data-field`. Capability rows are gated by META
|
|
6
6
|
// BOOLEANS: a new agent's sidecar drives its panel with no UI change.
|
|
7
7
|
import { resolveOrOutType } from '../../../src/shared/graph/ports.mjs';
|
|
8
|
+
import { DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, wirableParams, hasParamsPort } from '../../../src/shared/graph/script-meta.mjs';
|
|
9
|
+
// The DOM primitives and the two script forms live in ../script-forms.mjs so the
|
|
10
|
+
// composer, the Scripts page's Overview tab and the Test tab share ONE copy (C3).
|
|
11
|
+
import {
|
|
12
|
+
h, field, select, toggle, number, renderParamsForm, renderPortEditor,
|
|
13
|
+
} from '../script-forms.mjs';
|
|
8
14
|
|
|
9
15
|
const ARITY_KINDS = new Set(['and', 'or', 'combine']);
|
|
10
16
|
const FLOW_TITLES = { task: 'Task', end: 'End', and: 'AND', or: 'OR', combine: 'Combine' };
|
|
@@ -16,51 +22,12 @@ const FLOW_BLURB = {
|
|
|
16
22
|
combine: 'Joins its md inputs into one document, in port order.',
|
|
17
23
|
};
|
|
18
24
|
|
|
19
|
-
const h = (doc, tag, cls, text) => { const n = doc.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
|
|
20
|
-
const field = (doc, cls, label) => { const w = h(doc, 'div', `ins-f ${cls}`); w.appendChild(h(doc, 'label', 'ins-label', label)); return w; };
|
|
21
|
-
|
|
22
|
-
function select(doc, cls, name, label, items, value, { disabled = false, title = '' } = {}) {
|
|
23
|
-
const wrap = field(doc, cls, label);
|
|
24
|
-
if (title) wrap.title = title;
|
|
25
|
-
const sel = h(doc, 'select', 'ins-select');
|
|
26
|
-
sel.dataset.field = name;
|
|
27
|
-
sel.disabled = Boolean(disabled);
|
|
28
|
-
for (const opt of items) {
|
|
29
|
-
const o = doc.createElement('option');
|
|
30
|
-
o.value = opt.value; o.textContent = opt.text;
|
|
31
|
-
if (opt.value === (value == null ? '' : String(value))) o.selected = true;
|
|
32
|
-
sel.appendChild(o);
|
|
33
|
-
}
|
|
34
|
-
const shell = h(doc, 'span', 'ins-select-wrap'); // the product's .select-wrap idea: the chevron is a token-coloured ::after on a wrapper
|
|
35
|
-
shell.appendChild(sel);
|
|
36
|
-
wrap.appendChild(shell);
|
|
37
|
-
return wrap;
|
|
38
|
-
}
|
|
39
|
-
function toggle(doc, cls, name, label, hint, { checked = false, disabled = false, title = '' } = {}) {
|
|
40
|
-
const row = h(doc, 'div', `ins-tog ${cls}`);
|
|
41
|
-
if (title) row.title = title;
|
|
42
|
-
const box = doc.createElement('input');
|
|
43
|
-
box.type = 'checkbox'; box.dataset.field = name; box.checked = Boolean(checked); box.disabled = Boolean(disabled);
|
|
44
|
-
const body = h(doc, 'span', 'ins-tog-b');
|
|
45
|
-
body.appendChild(h(doc, 'span', 'ins-tog-t', label));
|
|
46
|
-
if (hint) body.appendChild(h(doc, 'small', 'ins-tog-h', hint));
|
|
47
|
-
row.append(box, body);
|
|
48
|
-
return row;
|
|
49
|
-
}
|
|
50
25
|
function head(doc, title, sub) {
|
|
51
26
|
const w = h(doc, 'div', 'ins-head');
|
|
52
27
|
w.appendChild(h(doc, 'div', 'ins-name', title));
|
|
53
28
|
if (sub) w.appendChild(h(doc, 'div', 'ins-sub', sub));
|
|
54
29
|
return w;
|
|
55
30
|
}
|
|
56
|
-
function number(doc, cls, name, label, value, min) {
|
|
57
|
-
const wrap = field(doc, cls, label);
|
|
58
|
-
const input = doc.createElement('input');
|
|
59
|
-
input.type = 'number'; input.className = 'ins-number'; input.dataset.field = name;
|
|
60
|
-
input.min = String(min); input.step = '1'; input.value = String(value);
|
|
61
|
-
wrap.appendChild(input);
|
|
62
|
-
return wrap;
|
|
63
|
-
}
|
|
64
31
|
/** Read-only listing of a node's resolved ports. */
|
|
65
32
|
function portList(doc, ports) {
|
|
66
33
|
const wrap = h(doc, 'div', 'ins-ports');
|
|
@@ -73,7 +40,7 @@ function portList(doc, ports) {
|
|
|
73
40
|
item.appendChild(h(doc, 'i', p.synthetic ? 'gdot' : `dot ${p.type}`));
|
|
74
41
|
item.appendChild(h(doc, 'span', 'pn', p.id));
|
|
75
42
|
const bits = [p.type];
|
|
76
|
-
if (p.synthetic) bits.push('engine');
|
|
43
|
+
if (p.synthetic || p.engine) bits.push('engine');
|
|
77
44
|
else if (dir === 'in') bits.push(p.loop ? 'loop' : (p.required === false ? 'optional' : 'required'));
|
|
78
45
|
else if (p.when && p.when !== 'always') bits.push(`on ${p.when}`);
|
|
79
46
|
if (p.expands) bits.push('fan-out');
|
|
@@ -86,10 +53,9 @@ function portList(doc, ports) {
|
|
|
86
53
|
zone('Outputs', ports.outputs, 'out');
|
|
87
54
|
return wrap;
|
|
88
55
|
}
|
|
89
|
-
|
|
90
|
-
export function renderNodeInspector(node, { template, portsFn, meta = null, models = [], efforts = [], subagentModels = [], doc = globalThis.document } = {}) {
|
|
56
|
+
export function renderNodeInspector(node, { template, portsFn, meta = null, models = [], efforts = [], subagentModels = [], editorFor = null, doc = globalThis.document } = {}) {
|
|
91
57
|
const ports = portsFn(node) || { inputs: [], outputs: [] };
|
|
92
|
-
const root = h(doc, 'div', `ins-panel ins-${node.kind === 'agent' ? 'agent' : `flow ins-${node.kind}`}`);
|
|
58
|
+
const root = h(doc, 'div', `ins-panel ins-${node.kind === 'agent' ? 'agent' : node.kind === 'script' ? 'script' : `flow ins-${node.kind}`}`);
|
|
93
59
|
root.dataset.nodeId = node.id;
|
|
94
60
|
const body = h(doc, 'div', 'ins-body-in');
|
|
95
61
|
|
|
@@ -103,8 +69,8 @@ export function renderNodeInspector(node, { template, portsFn, meta = null, mode
|
|
|
103
69
|
body.appendChild(select(doc, 'ins-effort', 'effort', 'Effort',
|
|
104
70
|
[{ value: '', text: 'default' }, ...efforts.map((e) => ({ value: e, text: e }))], node.config.effort));
|
|
105
71
|
if (meta && meta.fanOut) {
|
|
106
|
-
body.appendChild(toggle(doc, 'ins-fanout', 'fanOut', 'Research fan-out', 'parallel research sub-agents',
|
|
107
|
-
{ checked: node.config.fanOut === true }));
|
|
72
|
+
body.appendChild(lv(toggle(doc, 'ins-fanout', 'fanOut', 'Research fan-out', 'parallel research sub-agents',
|
|
73
|
+
{ checked: node.config.fanOut === true }), 'expert', node.config.fanOut === true));
|
|
108
74
|
// What this node's sub-agents run on. Gated by the SAME meta flag as the
|
|
109
75
|
// toggle above: an agent that cannot fan out has no children to place.
|
|
110
76
|
// '' = unset — the run resolves the auto default (a per-spawn choice
|
|
@@ -117,7 +83,7 @@ export function renderNodeInspector(node, { template, portsFn, meta = null, mode
|
|
|
117
83
|
// the locked option carries the stored value — and the full control
|
|
118
84
|
// returns when the node moves back to a plain model.
|
|
119
85
|
const chosen = node.config.model ? models.find((m) => m && m.id === node.config.model) : null;
|
|
120
|
-
body.appendChild(chosen && chosen.routed
|
|
86
|
+
body.appendChild(lv(chosen && chosen.routed
|
|
121
87
|
? select(doc, 'ins-subagent', 'subagentModel', 'Sub-agent model',
|
|
122
88
|
[{ value: node.config.subagentModel || '', text: 'same endpoint (locked)' }],
|
|
123
89
|
node.config.subagentModel || '',
|
|
@@ -125,19 +91,44 @@ export function renderNodeInspector(node, { template, portsFn, meta = null, mode
|
|
|
125
91
|
: select(doc, 'ins-subagent', 'subagentModel', 'Sub-agent model',
|
|
126
92
|
[{ value: '', text: 'default (agent picks)' },
|
|
127
93
|
...subagentModels.map((m) => ({ value: m, text: m === 'auto' ? 'agent picks' : m }))],
|
|
128
|
-
node.config.subagentModel));
|
|
94
|
+
node.config.subagentModel), 'expert', !!node.config.subagentModel));
|
|
129
95
|
}
|
|
130
96
|
if (meta && meta.asksQuestions) {
|
|
131
97
|
const locked = Boolean(meta.questionsLocked);
|
|
132
98
|
const saved = node.config.askQuestions;
|
|
133
|
-
body.appendChild(toggle(doc, 'ins-questions', 'askQuestions', 'Ask questions', 'pauses the run for input', {
|
|
99
|
+
body.appendChild(lv(toggle(doc, 'ins-questions', 'askQuestions', 'Ask questions', 'pauses the run for input', {
|
|
134
100
|
checked: locked ? Boolean(meta.questionsDefault) : (typeof saved === 'boolean' ? saved : Boolean(meta.questionsDefault)),
|
|
135
101
|
disabled: locked,
|
|
136
102
|
title: locked ? (meta.questionsDefault ? 'Always on for this agent' : 'Always off for this agent') : '',
|
|
137
|
-
}));
|
|
103
|
+
}), 'expert', !locked && typeof saved === 'boolean'));
|
|
104
|
+
}
|
|
105
|
+
body.appendChild(lv(toggle(doc, 'ins-awaitall', 'awaitAll', 'Await all inputs', 'gate until every wire fires',
|
|
106
|
+
{ checked: node.config.awaitAll === true }), 'expert', node.config.awaitAll === true));
|
|
107
|
+
} else if (node.kind === 'script') {
|
|
108
|
+
root.appendChild(head(doc, (meta && meta.displayName) || node.key || node.id, `${node.key} · ${node.id}`));
|
|
109
|
+
const chips = h(doc, 'div', 'ins-chiprow');
|
|
110
|
+
chips.append(h(doc, 'span', 'badge', (meta && meta.origin) || 'builtin'), h(doc, 'span', 'chip rt', (meta && meta.runtime) || 'script'));
|
|
111
|
+
body.appendChild(chips);
|
|
112
|
+
body.appendChild(renderParamsForm(meta, node.config, { doc, editorFor }));
|
|
113
|
+
const ms = Number.isInteger(node.config.timeoutMs) ? node.config.timeoutMs : ((meta && meta.timeoutMs) || DEFAULT_TIMEOUT_MS);
|
|
114
|
+
// Interface mode (docs/ui-levels.md): what the card RUNS is never hidden; per-node tuning and ports are expert.
|
|
115
|
+
const timeout = number(doc, 'ins-timeout', 'timeoutMs', 'Timeout (s)', Math.round(ms / 1000), 1);
|
|
116
|
+
timeout.querySelector('input').max = String(MAX_TIMEOUT_MS / 1000);
|
|
117
|
+
body.appendChild(lv(timeout, 'expert', Number.isInteger(node.config.timeoutMs)));
|
|
118
|
+
body.appendChild(lv(toggle(doc, 'ins-awaitall', 'awaitAll', 'Await all inputs', 'gate until every wire fires',
|
|
119
|
+
{ checked: node.config.awaitAll === true }), 'expert', node.config.awaitAll === true));
|
|
120
|
+
// Offered only where ticking it takes effect: `meta` here is the registry entry, so the forced-on probe is honest.
|
|
121
|
+
// A ticked box always renders, so an opt-in V22 refuses (the script changed under the card) can still be un-ticked.
|
|
122
|
+
if (node.config.paramsPort === true || hasParamsPort(meta, { ...node.config, paramsPort: true })) {
|
|
123
|
+
body.appendChild(lv(toggle(doc, 'ins-paramsport', 'paramsPort', 'Params from a wire', `json sets: ${wirableParams(meta).map((p) => p.id).join(', ') || 'nothing'}`,
|
|
124
|
+
{ checked: node.config.paramsPort === true }), 'expert', node.config.paramsPort === true));
|
|
138
125
|
}
|
|
139
|
-
body.appendChild(
|
|
140
|
-
|
|
126
|
+
body.appendChild(lv(h(doc, 'div', 'ins-sep'), 'expert'));
|
|
127
|
+
body.appendChild(lv(meta && meta.ports === 'config'
|
|
128
|
+
? renderPortEditor(node.config.ports, { doc, hasVerdict: Boolean(meta && meta.verdict) })
|
|
129
|
+
: portList(doc, ports), 'expert'));
|
|
130
|
+
root.appendChild(body);
|
|
131
|
+
return root;
|
|
141
132
|
} else {
|
|
142
133
|
root.appendChild(head(doc, FLOW_TITLES[node.kind] || node.kind, node.id));
|
|
143
134
|
body.appendChild(h(doc, 'p', 'ins-blurb', FLOW_BLURB[node.kind] || ''));
|
|
@@ -152,28 +143,40 @@ export function renderNodeInspector(node, { template, portsFn, meta = null, mode
|
|
|
152
143
|
body.appendChild(h(doc, 'div', 'ins-resolved', resolved ? `forwards: ${resolved}` : 'unresolved'));
|
|
153
144
|
}
|
|
154
145
|
if (node.kind === 'task') {
|
|
155
|
-
body.appendChild(toggle(doc, 'ins-seed', 'planStoreSeed', 'Seed the plan store', 'treat an attached plan as the run’s plan',
|
|
156
|
-
{ checked: node.config.planStoreSeed === true }));
|
|
146
|
+
body.appendChild(lv(toggle(doc, 'ins-seed', 'planStoreSeed', 'Seed the plan store', 'treat an attached plan as the run’s plan',
|
|
147
|
+
{ checked: node.config.planStoreSeed === true }), 'expert', node.config.planStoreSeed === true));
|
|
157
148
|
}
|
|
158
149
|
if (node.kind === 'end') body.appendChild(h(doc, 'div', 'ins-result', ''));
|
|
159
150
|
}
|
|
160
|
-
body.appendChild(h(doc, 'div', 'ins-sep'));
|
|
161
|
-
body.appendChild(portList(doc, ports));
|
|
151
|
+
body.appendChild(lv(h(doc, 'div', 'ins-sep'), 'expert'));
|
|
152
|
+
body.appendChild(lv(portList(doc, ports), 'expert'));
|
|
162
153
|
root.appendChild(body);
|
|
163
154
|
return root;
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
/** Loop wires carry the per-wire cycle budget; a plain wire must NOT expose one
|
|
167
158
|
* (maxCycles on a non-loop wire is V13's error). */
|
|
159
|
+
/** Interface mode (docs/ui-levels.md): model and effort are the advanced inspector; everything else
|
|
160
|
+
* here is expert. `keep` = the control holds a non-default value, so it stays on screen. */
|
|
161
|
+
function lv(el, min, keep = false) {
|
|
162
|
+
el.dataset.minLevel = min;
|
|
163
|
+
if (keep) el.dataset.levelKeep = '1';
|
|
164
|
+
return el;
|
|
165
|
+
}
|
|
166
|
+
|
|
168
167
|
export function renderWireInspector(wire, { loop = false, doc = globalThis.document } = {}) {
|
|
169
168
|
const root = h(doc, 'div', `ins-panel ins-wire${loop ? ' ins-loop' : ''}`);
|
|
170
169
|
root.dataset.wireId = wire.id;
|
|
171
170
|
root.appendChild(head(doc, loop ? 'Loop wire' : 'Wire', `${wire.from.node}.${wire.from.port} → ${wire.to.node}.${wire.to.port}`));
|
|
172
171
|
const body = h(doc, 'div', 'ins-body-in');
|
|
173
172
|
if (loop) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
const budgeted = !!(wire.config && Number.isInteger(wire.config.maxCycles));
|
|
174
|
+
body.appendChild(lv(number(doc, 'ins-maxcycles', 'maxCycles', 'Max cycles',
|
|
175
|
+
budgeted ? wire.config.maxCycles : 3, 1), 'expert', budgeted));
|
|
176
|
+
body.appendChild(lv(h(doc, 'small', 'ins-hint', 'How many times this loop may re-deliver before the gate asks.'), 'expert', budgeted));
|
|
177
|
+
const plain = h(doc, 'p', 'ins-blurb ins-loop-plain', 'A loop: work flows back here until it passes, up to a cycle limit. Expert mode sets the limit.');
|
|
178
|
+
plain.dataset.maxLevel = 'advanced';
|
|
179
|
+
body.appendChild(plain);
|
|
177
180
|
} else {
|
|
178
181
|
body.appendChild(h(doc, 'p', 'ins-blurb', 'Plain data wire. Delete it to rewire the target input.'));
|
|
179
182
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// ui/public/graph/palette.mjs
|
|
2
|
-
// The rail's Agents tab: one DISCLOSURE per domain (plus the PINNED Flow group),
|
|
2
|
+
// The rail's Agents tab: one DISCLOSURE per domain (plus the Scripts group and the PINNED Flow group),
|
|
3
3
|
// a filter, and agent pills stacked one per row. Pure DOM + one delegated
|
|
4
4
|
// controller; it never touches the template — it calls back into the composer,
|
|
5
5
|
// which owns every mutation.
|
|
6
6
|
export const FLOW_GROUP = 'flow';
|
|
7
|
+
/** The Scripts group (D4): its own disclosure between the domain groups and the pinned Flow group. */
|
|
8
|
+
export const SCRIPTS_GROUP = 'scripts';
|
|
7
9
|
/** Flow pills advertise their ports the way agent pills do (pill line 2). */
|
|
8
10
|
export const FLOW_PORT_LINE = {
|
|
9
11
|
task: 'source · out task',
|
|
@@ -34,7 +36,7 @@ export function portLineOf(entry) {
|
|
|
34
36
|
* then `general`, then the pinned Flow group. `shared` agents are folded into
|
|
35
37
|
* every domain group; `placeable:false` agents are dropped everywhere (that is
|
|
36
38
|
* how workspaceScanner never reaches a canvas). Empty groups are omitted. */
|
|
37
|
-
export function paletteEntries(agents, { placedKinds = [] } = {}) {
|
|
39
|
+
export function paletteEntries(agents, { scripts = [], placedKinds = [] } = {}) {
|
|
38
40
|
const list = (Array.isArray(agents) ? agents : []).filter((a) => a && a.placeable !== false)
|
|
39
41
|
.map((a) => ({ ...a, order: typeof a.order === 'number' ? a.order : 99, domain: a.domain || 'general' }));
|
|
40
42
|
const shared = list.filter((a) => a.domain === 'shared');
|
|
@@ -45,6 +47,13 @@ export function paletteEntries(agents, { placedKinds = [] } = {}) {
|
|
|
45
47
|
const groups = domains
|
|
46
48
|
.map((domain) => ({ domain, flow: false, agents: [...shared, ...list.filter((a) => a.domain === domain)].sort(byOrder) }))
|
|
47
49
|
.filter((g) => g.agents.length);
|
|
50
|
+
// Scripts: one group, ordered by `order`, placeable only; a config-ported
|
|
51
|
+
// script advertises "in/out (per card)" because its ports live on the card.
|
|
52
|
+
const scriptList = (Array.isArray(scripts) ? scripts : []).filter((s) => s && s.placeable !== false)
|
|
53
|
+
.map((s) => ({ ...s, kind: 'script', order: typeof s.order === 'number' ? s.order : 99 }))
|
|
54
|
+
.sort(byOrder)
|
|
55
|
+
.map((s) => ({ ...s, chip: s.runtime || '', portLine: s.ports === 'config' ? 'in/out (per card)' : portLineOf(s) }));
|
|
56
|
+
if (scriptList.length) groups.push({ domain: SCRIPTS_GROUP, scripts: true, flow: false, agents: scriptList });
|
|
48
57
|
const placed = new Set(placedKinds);
|
|
49
58
|
groups.push({
|
|
50
59
|
domain: FLOW_GROUP, flow: true,
|
|
@@ -63,17 +72,24 @@ function h(doc, tag, cls, text) {
|
|
|
63
72
|
function pill(doc, entry) {
|
|
64
73
|
const btn = h(doc, 'button', 'ap');
|
|
65
74
|
btn.type = 'button';
|
|
66
|
-
|
|
75
|
+
const script = entry.kind === 'script';
|
|
76
|
+
if (entry.key) { btn.dataset.key = entry.key; if (script) { btn.dataset.kind = 'script'; btn.dataset.rt = entry.chip || ''; } }
|
|
77
|
+
else btn.dataset.kind = entry.kind;
|
|
67
78
|
btn.disabled = Boolean(entry.disabled);
|
|
68
79
|
if (entry.disabled) btn.classList.add('dim');
|
|
69
80
|
const dot = h(doc, 'span', 'd');
|
|
70
|
-
dot.dataset.color = entry.kind ? 'flow' : (entry.color || 'blue');
|
|
81
|
+
dot.dataset.color = entry.kind && !script ? 'flow' : (entry.color || 'blue');
|
|
71
82
|
const body = h(doc, 'span', 'b');
|
|
72
|
-
|
|
83
|
+
const nrow = h(doc, 'span', 'nrow');
|
|
84
|
+
nrow.appendChild(h(doc, 'span', 'n', entry.displayName || entry.key || entry.kind));
|
|
85
|
+
if (script && entry.chip) nrow.appendChild(h(doc, 'span', 'chip rt', entry.chip));
|
|
86
|
+
body.append(nrow,
|
|
73
87
|
h(doc, 'span', 'p pt', entry.portLine != null ? entry.portLine : portLineOf(entry)));
|
|
74
88
|
btn.append(dot, body);
|
|
75
89
|
if (entry.disabled) btn.appendChild(h(doc, 'span', 'chip', '1 placed'));
|
|
76
90
|
if (entry.description) btn.title = entry.description;
|
|
91
|
+
// Logic nodes are expert building blocks (docs/ui-levels.md); Task and End stay with everyone.
|
|
92
|
+
if (entry.kind && !SINGLETON_KINDS.has(entry.kind)) btn.dataset.minLevel = 'expert';
|
|
77
93
|
return btn;
|
|
78
94
|
}
|
|
79
95
|
|
|
@@ -92,19 +108,21 @@ function chevron(doc) {
|
|
|
92
108
|
return svg;
|
|
93
109
|
}
|
|
94
110
|
|
|
95
|
-
export function renderPalette(host, { agents = [], placedKinds = [], collapsed = new Set(), query = '', doc = globalThis.document } = {}) {
|
|
111
|
+
export function renderPalette(host, { agents = [], scripts = [], placedKinds = [], collapsed = new Set(), query = '', doc = globalThis.document } = {}) {
|
|
96
112
|
if (!host) return;
|
|
97
|
-
const groups = paletteEntries(agents, { placedKinds });
|
|
113
|
+
const groups = paletteEntries(agents, { scripts, placedKinds });
|
|
98
114
|
const frag = doc.createDocumentFragment();
|
|
99
115
|
for (const g of groups) {
|
|
100
116
|
const sec = h(doc, 'section', `pal-group${g.flow ? ' pal-pinned' : ''}`);
|
|
101
117
|
sec.dataset.domain = g.domain;
|
|
118
|
+
// Placing a card that runs a command is authoring: expert (docs/ui-levels.md).
|
|
119
|
+
if (g.scripts) sec.dataset.minLevel = 'expert';
|
|
102
120
|
// The group HEAD is the control: there is no separate chip row, so a domain
|
|
103
121
|
// can only be reached — and only be folded away — through its own header.
|
|
104
122
|
const head = h(doc, 'button', 'pal-grp');
|
|
105
123
|
head.type = 'button';
|
|
106
124
|
head.dataset.domain = g.domain;
|
|
107
|
-
head.append(chevron(doc), h(doc, 'span', 'lab', g.flow ? 'Flow' : g.domain), h(doc, 'span', 'chip', String(g.agents.length)));
|
|
125
|
+
head.append(chevron(doc), h(doc, 'span', 'lab', g.flow ? 'Flow' : g.scripts ? 'Scripts' : g.domain), h(doc, 'span', 'chip', String(g.agents.length)));
|
|
108
126
|
if (g.flow) head.appendChild(h(doc, 'span', 'chip pinned-tag', 'pinned'));
|
|
109
127
|
const pills = h(doc, 'div', 'pills');
|
|
110
128
|
for (const a of g.agents) pills.appendChild(pill(doc, a));
|
|
@@ -129,7 +147,7 @@ export function applyFilter(host, query, collapsed = new Set()) {
|
|
|
129
147
|
for (const sec of host.querySelectorAll('.pal-group')) {
|
|
130
148
|
let any = false;
|
|
131
149
|
for (const btn of sec.querySelectorAll('.ap')) {
|
|
132
|
-
const hay = `${btn.querySelector('.n').textContent} ${btn.dataset.key || btn.dataset.kind || ''} ${btn.querySelector('.p').textContent}`.toLowerCase();
|
|
150
|
+
const hay = `${btn.querySelector('.n').textContent} ${btn.dataset.key || btn.dataset.kind || ''} ${btn.dataset.rt || ''} ${btn.querySelector('.p').textContent}`.toLowerCase();
|
|
133
151
|
const show = !q || hay.includes(q);
|
|
134
152
|
btn.hidden = !show;
|
|
135
153
|
if (show) any = true;
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
// labels read the manifest's port metadata (`loop: true`), statuses read the
|
|
8
8
|
// execution ledger (state.steps[], one row per execution, key === executionId),
|
|
9
9
|
// colours read the manifest node. History renders with the registry absent.
|
|
10
|
+
import { levelAtLeast } from '../ui-level.mjs';
|
|
10
11
|
import { manifestPortsFn, manifestTemplate } from '../../../src/shared/graph/manifest.mjs';
|
|
11
|
-
import { BOOKEND_EXECUTION_IDS, DEFAULT_MAX_CYCLES } from '../../../src/shared/graph/constants.mjs';
|
|
12
|
+
import { BOOKEND_EXECUTION_IDS, DEFAULT_MAX_CYCLES, KEYED_KINDS } from '../../../src/shared/graph/constants.mjs';
|
|
12
13
|
import { fanLines } from '../../../src/shared/graph/geometry.mjs';
|
|
13
14
|
|
|
14
15
|
/** The run-level warning a run that drained without binding End carries. */
|
|
@@ -69,7 +70,9 @@ export function manifestWires(stepper) {
|
|
|
69
70
|
export function manifestAgents(stepper) {
|
|
70
71
|
const out = {};
|
|
71
72
|
for (const n of manifestNodes(stepper)) {
|
|
72
|
-
if (n.kind
|
|
73
|
+
if (KEYED_KINDS.includes(n.kind) && n.key && !out[n.key]) {
|
|
74
|
+
out[n.key] = { displayName: n.label || n.key, color: n.color || '', icon: n.icon || '', ...(n.kind === 'script' ? { runtime: n.runtime || '' } : {}) };
|
|
75
|
+
}
|
|
73
76
|
}
|
|
74
77
|
return out;
|
|
75
78
|
}
|
|
@@ -153,7 +156,7 @@ export function statusOf(node, rows, ctx) {
|
|
|
153
156
|
* @param {object} st { stepper, status, steps, active, endReached, result, warnings, wireDeliveries, tokens, gate }
|
|
154
157
|
* @param {{live?:boolean, now?:number, subsOf?:(nodeId:string)=>Array}} opts
|
|
155
158
|
*/
|
|
156
|
-
export function decorFromState(st, { live = true, now = Date.now(), subsOf = null } = {}) {
|
|
159
|
+
export function decorFromState(st, { live = true, now = Date.now(), subsOf = null, lastLines = null } = {}) {
|
|
157
160
|
const state = st || {};
|
|
158
161
|
const stepper = state.stepper || null;
|
|
159
162
|
const nodes = manifestNodes(stepper);
|
|
@@ -186,9 +189,9 @@ export function decorFromState(st, { live = true, now = Date.now(), subsOf = nul
|
|
|
186
189
|
colors[node.id] = node.color || '';
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
// Progress = done
|
|
190
|
-
const
|
|
191
|
-
const progress = { done:
|
|
192
|
+
// Progress = done KEYED nodes / keyed nodes (D15: a number, never a bar). A script is a keyed card.
|
|
193
|
+
const keyed = nodes.filter((n) => KEYED_KINDS.includes(n.kind));
|
|
194
|
+
const progress = { done: keyed.filter((n) => status[n.id] === 'done').length, total: keyed.length };
|
|
192
195
|
|
|
193
196
|
// Active nodes, most recently started FIRST (the compact row and the pill name
|
|
194
197
|
// the newest one; two or more collapse to "N agents running").
|
|
@@ -226,7 +229,7 @@ export function decorFromState(st, { live = true, now = Date.now(), subsOf = nul
|
|
|
226
229
|
executions: rows.length, loopDeliveries,
|
|
227
230
|
nodeIds: nodes.map((n) => n.id), wireIds: wires.map((w) => w.id), expanded: null,
|
|
228
231
|
};
|
|
229
|
-
decorateExecutions(decor, { stepper, nodes, wires, grouped, rows, activeList, rowFor, stepByExec, state, now, live, subsOf });
|
|
232
|
+
decorateExecutions(decor, { stepper, nodes, wires, grouped, rows, activeList, rowFor, stepByExec, state, now, live, subsOf, lastLines, status });
|
|
230
233
|
return decor;
|
|
231
234
|
}
|
|
232
235
|
|
|
@@ -289,26 +292,29 @@ export function execBandLayout(label, right) {
|
|
|
289
292
|
}
|
|
290
293
|
|
|
291
294
|
function decorateExecutions(decor, ctx) {
|
|
292
|
-
const { nodes, wires, grouped, activeList, rowFor, state, now, live, subsOf } = ctx;
|
|
295
|
+
const { nodes, wires, grouped, activeList, rowFor, state, now, live, subsOf, lastLines, status } = ctx;
|
|
293
296
|
|
|
294
297
|
for (const node of nodes) {
|
|
295
298
|
const list = grouped.get(node.id) || [];
|
|
296
299
|
// A FLOW node (task/and/or/combine/end) executes instantly and for free: its
|
|
297
300
|
// rows carry no duration and no cost pill, and its card no header totals.
|
|
298
|
-
const flow = node.kind
|
|
301
|
+
const flow = !KEYED_KINDS.includes(node.kind);
|
|
302
|
+
// A SCRIPT runs a child process: real duration, no cost pill — an exit code where an agent shows cost (S5).
|
|
303
|
+
const script = node.kind === 'script';
|
|
299
304
|
const rows = list.map((row) => {
|
|
300
305
|
// A flow node executes instantly and for free: the NUMBERS are zeroed with
|
|
301
306
|
// the pill texts, so `durMs`/`costUsd` never contradict `dur`/`cost` (a
|
|
302
307
|
// consumer that sums the numbers must not pick up a flow row's real ms).
|
|
303
308
|
const durMs = flow ? 0 : rowMs(row, now, live);
|
|
304
|
-
const costUsd = flow ? 0 : round2(row.costUsd);
|
|
309
|
+
const costUsd = flow || script ? 0 : round2(row.costUsd);
|
|
305
310
|
return {
|
|
306
311
|
executionId: row.executionId, nodeId: node.id,
|
|
307
312
|
kind: row.kind === 'task' ? 'task' : 'cycle',
|
|
308
313
|
ordinal: Number(row.ordinal ?? row.cycle) || 1,
|
|
309
314
|
label: rowLabel(node, row), led: ledOf(row.status),
|
|
310
315
|
dur: !flow && row.activeMs != null ? fmtDur(durMs) : '',
|
|
311
|
-
cost: flow ? '' : fmtUsd(costUsd),
|
|
316
|
+
cost: flow || script ? '' : fmtUsd(costUsd),
|
|
317
|
+
exit: script && row.exitCode != null ? `exit ${row.exitCode}` : '',
|
|
312
318
|
durMs, costUsd, flow,
|
|
313
319
|
};
|
|
314
320
|
});
|
|
@@ -321,11 +327,17 @@ function decorateExecutions(decor, ctx) {
|
|
|
321
327
|
if (rows.length || fan) {
|
|
322
328
|
decor.footers[node.id] = { rows, summary: stripText(rows), leds: rows.map((r) => r.led), fan };
|
|
323
329
|
}
|
|
330
|
+
// S4: the last captured line of a RUNNING script, when the page has one.
|
|
331
|
+
const liveLine = script && live && status[node.id] === 'active' && lastLines && typeof lastLines.get === 'function' ? lastLines.get(node.id) : null;
|
|
332
|
+
if (liveLine) {
|
|
333
|
+
if (!decor.footers[node.id]) decor.footers[node.id] = { rows: [], summary: '', leds: [], fan: null };
|
|
334
|
+
decor.footers[node.id].live = String(liveLine);
|
|
335
|
+
}
|
|
324
336
|
if (rows.length && !flow) {
|
|
325
337
|
const durMs = rows.reduce((a, r) => a + r.durMs, 0);
|
|
326
338
|
const costUsd = sumUsd(rows);
|
|
327
339
|
decor.totals[node.id] = {
|
|
328
|
-
durMs, dur: fmtDur(durMs), costUsd, cost: fmtUsd(costUsd),
|
|
340
|
+
durMs, dur: fmtDur(durMs), costUsd, cost: script ? '' : fmtUsd(costUsd),
|
|
329
341
|
hasStep: rows.some((r) => r.dur !== ''),
|
|
330
342
|
};
|
|
331
343
|
}
|
|
@@ -376,16 +388,20 @@ function decorateExecutions(decor, ctx) {
|
|
|
376
388
|
*/
|
|
377
389
|
export function applyDecor(view, decor) {
|
|
378
390
|
if (!view || !decor) return;
|
|
379
|
-
|
|
391
|
+
// Interface mode (docs/ui-levels.md): status colours, the gate pip, the End result and live wires
|
|
392
|
+
// are for everyone; per-node totals, the fan and execution strips and loop-count badges are
|
|
393
|
+
// expert detail. Gated HERE, not in CSS — a footer band sets the card's height.
|
|
394
|
+
const full = decor.detail !== undefined ? !!decor.detail : levelAtLeast('expert');
|
|
395
|
+
const expanded = full ? (decor.expanded || null) : null;
|
|
380
396
|
for (const nodeId of decor.nodeIds || []) {
|
|
381
397
|
view.setStatus(nodeId, decor.status[nodeId] || 'pending');
|
|
382
398
|
view.setNodeChrome(nodeId, {
|
|
383
399
|
color: decor.colors[nodeId] || '',
|
|
384
400
|
gate: decor.gate && decor.gate.nodeId === nodeId
|
|
385
401
|
? { wireId: decor.gate.wireId, title: 'waiting on a loop gate — open the question panel' } : null,
|
|
386
|
-
totals: decor.totals[nodeId] || null,
|
|
402
|
+
totals: full ? (decor.totals[nodeId] || null) : null,
|
|
387
403
|
});
|
|
388
|
-
const foot = decor.footers[nodeId] || null;
|
|
404
|
+
const foot = full ? (decor.footers[nodeId] || null) : null;
|
|
389
405
|
const bands = [];
|
|
390
406
|
if (foot && foot.fan) {
|
|
391
407
|
bands.push({ kind: 'fan', leds: foot.fan.leds, count: foot.fan.count, lines: fanLines(foot.fan.leds.length) });
|
|
@@ -394,17 +410,18 @@ export function applyDecor(view, decor) {
|
|
|
394
410
|
bands.push({ kind: 'strip', leds: foot.leds.slice(0, STRIP_LED_CAP), summary: foot.summary, expanded: expanded === nodeId });
|
|
395
411
|
if (expanded === nodeId) {
|
|
396
412
|
for (const r of foot.rows) {
|
|
397
|
-
const right = [r.dur, r.cost].filter(Boolean).join(' · ');
|
|
413
|
+
const right = [r.dur, r.cost || r.exit].filter(Boolean).join(' · ');
|
|
398
414
|
bands.push({ kind: 'exec', executionId: r.executionId, led: r.led, label: r.label,
|
|
399
415
|
right, ...execBandLayout(r.label, right) });
|
|
400
416
|
}
|
|
401
417
|
}
|
|
402
418
|
}
|
|
419
|
+
if (foot && foot.live) bands.push({ kind: 'live', text: foot.live });
|
|
403
420
|
if (decor.endResult && decor.endResult.nodeId === nodeId) {
|
|
404
421
|
bands.push({ kind: 'result', text: decor.endResult.text, path: decor.endResult.path });
|
|
405
422
|
}
|
|
406
423
|
view.setFooter(nodeId, bands);
|
|
407
424
|
}
|
|
408
|
-
for (const wireId of decor.wireIds || []) view.setWireBadge(wireId, decor.loopBadges[wireId] || null);
|
|
425
|
+
for (const wireId of decor.wireIds || []) view.setWireBadge(wireId, full ? (decor.loopBadges[wireId] || null) : null);
|
|
409
426
|
view.setWireLive(decor.liveWireIds || []);
|
|
410
427
|
}
|
|
@@ -134,6 +134,7 @@ export function mountRunGraph(hostEl, opts = {}) {
|
|
|
134
134
|
}
|
|
135
135
|
function buildNav() {
|
|
136
136
|
nav = doc.createElement('div');
|
|
137
|
+
nav.dataset.minLevel = 'advanced'; // zoom cluster: interface mode (docs/ui-levels.md)
|
|
137
138
|
nav.className = 'gv-nav';
|
|
138
139
|
for (const [key, label, glyph] of NAV_BTNS) {
|
|
139
140
|
const b = doc.createElement('button');
|
|
@@ -214,6 +215,7 @@ export function mountRunGraph(hostEl, opts = {}) {
|
|
|
214
215
|
if (!isStatic) {
|
|
215
216
|
const hint = doc.createElement('div');
|
|
216
217
|
hint.className = 'rg-hint';
|
|
218
|
+
hint.dataset.minLevel = 'advanced';
|
|
217
219
|
hint.textContent = HINT_TEXT;
|
|
218
220
|
wrap.appendChild(hint);
|
|
219
221
|
buildNav();
|
|
@@ -258,5 +260,9 @@ export function mountRunGraph(hostEl, opts = {}) {
|
|
|
258
260
|
bound = false;
|
|
259
261
|
}
|
|
260
262
|
|
|
261
|
-
|
|
263
|
+
/** Re-run the decor pass with the bag already held (an interface-mode change alters which
|
|
264
|
+
* footer bands show, hence the card heights, hence the fit). */
|
|
265
|
+
function repaint() { paint(); refit(); }
|
|
266
|
+
|
|
267
|
+
return { update, fit, repaint, destroy, get view() { return view; } };
|
|
262
268
|
}
|
|
@@ -22,6 +22,9 @@ export function renderSaveDialog({ name = '', domain = '', domains = [], title =
|
|
|
22
22
|
form.appendChild(nameWrap);
|
|
23
23
|
|
|
24
24
|
const domWrap = h(doc, 'div', 'sd-field');
|
|
25
|
+
// Grouping by domain is expert housekeeping (docs/ui-levels.md); a non-default one stays visible.
|
|
26
|
+
domWrap.dataset.minLevel = 'expert';
|
|
27
|
+
if (domain && domain !== 'general') domWrap.dataset.levelKeep = '1';
|
|
25
28
|
domWrap.appendChild(h(doc, 'label', null, 'Domain'));
|
|
26
29
|
const domInput = doc.createElement('input');
|
|
27
30
|
domInput.type = 'text'; domInput.className = 'sd-domain'; domInput.value = domain;
|
package/ui/public/graph/view.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { portsOf, resolveOrOutType } from '../../../src/shared/graph/ports.mjs';
|
|
|
26
26
|
import { classifyLoops } from '../../../src/shared/graph/loops.mjs';
|
|
27
27
|
import { thumbnailSvg } from '../../../src/shared/graph/thumbnail.mjs';
|
|
28
28
|
import { sanitizeIcon } from '../../../src/shared/graph/manifest.mjs';
|
|
29
|
+
import { KEYED_KINDS } from '../../../src/shared/graph/constants.mjs';
|
|
29
30
|
|
|
30
31
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
31
32
|
|
|
@@ -77,6 +78,10 @@ export function safeAgentIcon(meta) {
|
|
|
77
78
|
return sanitizeIcon(raw) || USER_AGENT_ICON;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// The ƒ glyph lives with the icon set (one source for the page's tile and this canvas).
|
|
82
|
+
import { SCRIPT_GLYPH } from '../../../src/shared/graph/script-icons.mjs';
|
|
83
|
+
export { SCRIPT_GLYPH };
|
|
84
|
+
|
|
80
85
|
const dotClass = (t) => `dot ${t === 'md' || t === 'json' || t === 'void' || t === 'any' ? t : 'md'}`;
|
|
81
86
|
const whenCaption = (w) => (w === 'blocking' ? 'on blocking' : w === 'clean' ? 'on clean' : '');
|
|
82
87
|
|
|
@@ -199,17 +204,19 @@ export function createGraphView(host, {
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
function headerOf(node) {
|
|
202
|
-
if (node.kind
|
|
203
|
-
const meta = agents[node.key] || null;
|
|
207
|
+
if (KEYED_KINDS.includes(node.kind)) {
|
|
208
|
+
const meta = agents[node.key] || null; // the MERGED key -> meta index (agents + scripts)
|
|
209
|
+
const script = node.kind === 'script';
|
|
204
210
|
return {
|
|
205
|
-
cls: `h-${(meta && meta.color) || 'blue'}`,
|
|
211
|
+
cls: `h-${(meta && meta.color) || (script ? 'amber' : 'blue')}`,
|
|
206
212
|
title: (meta && meta.displayName) || node.key || node.id,
|
|
207
|
-
icon: safeAgentIcon(meta),
|
|
213
|
+
icon: safeAgentIcon(meta) || (script ? SCRIPT_GLYPH : ''),
|
|
208
214
|
viewBox: AGENT_VIEWBOX,
|
|
215
|
+
chip: script ? ((meta && meta.runtime) || 'script') : '',
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
218
|
const flow = FLOW_META[node.kind] || { title: node.kind, icon: '' };
|
|
212
|
-
return { cls: 'h-flow', title: flow.title, icon: flow.icon, viewBox: FLOW_VIEWBOX };
|
|
219
|
+
return { cls: 'h-flow', title: flow.title, icon: flow.icon, viewBox: FLOW_VIEWBOX, chip: '' };
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
function portRow(port, dir, resolvedType) {
|
|
@@ -310,6 +317,12 @@ export function createGraphView(host, {
|
|
|
310
317
|
row.append(h('i', 'led'), h('span', 'xl', band.label || ''), h('span', 'xr', band.right || ''));
|
|
311
318
|
return row;
|
|
312
319
|
}
|
|
320
|
+
if (band.kind === 'live') {
|
|
321
|
+
const l = h('div', 'xlive mono');
|
|
322
|
+
l.textContent = band.text || '';
|
|
323
|
+
l.title = band.text || '';
|
|
324
|
+
return l;
|
|
325
|
+
}
|
|
313
326
|
const res = h('div', 'xresult'); // kind: 'result'
|
|
314
327
|
if (!band.path) { res.textContent = band.text || ''; return res; }
|
|
315
328
|
const a = h('a', null, band.text || '');
|
|
@@ -363,6 +376,7 @@ export function createGraphView(host, {
|
|
|
363
376
|
if (sum && sum.textContent !== t) sum.textContent = t;
|
|
364
377
|
return true;
|
|
365
378
|
}
|
|
379
|
+
if (band.kind === 'live') { if (el.textContent !== band.text) { el.textContent = band.text || ''; el.title = band.text || ''; } return true; }
|
|
366
380
|
if (band.kind === 'exec') {
|
|
367
381
|
const cls = execRowClass(band);
|
|
368
382
|
if (el.className !== cls) el.className = cls;
|
|
@@ -427,7 +441,7 @@ export function createGraphView(host, {
|
|
|
427
441
|
el.style.height = `${box.h}px`;
|
|
428
442
|
const head = el.querySelector(':scope > .nhead');
|
|
429
443
|
const hd = headerOf(node);
|
|
430
|
-
const sig = `${hd.cls}|${hd.title}|${hd.icon}`;
|
|
444
|
+
const sig = `${hd.cls}|${hd.title}|${hd.icon}|${hd.chip}`;
|
|
431
445
|
if (head.dataset.sig !== sig) {
|
|
432
446
|
head.dataset.sig = sig;
|
|
433
447
|
head.className = `nhead ${hd.cls}`;
|
|
@@ -438,7 +452,9 @@ export function createGraphView(host, {
|
|
|
438
452
|
icon.innerHTML = hd.icon;
|
|
439
453
|
const tt = h('span', 'tt', hd.title);
|
|
440
454
|
tt.title = hd.title; // A35: an ellipsised name keeps its tooltip
|
|
441
|
-
|
|
455
|
+
const kids = [icon, tt];
|
|
456
|
+
if (hd.chip) kids.push(h('span', 'chip rt', hd.chip));
|
|
457
|
+
head.replaceChildren(...kids);
|
|
442
458
|
}
|
|
443
459
|
paintBand(el, node);
|
|
444
460
|
paintBody(el, node, p, orType, awaitWired);
|
|
@@ -23,9 +23,20 @@ export function guardrailSummary(s) {
|
|
|
23
23
|
function originBadge(doc, origin) {
|
|
24
24
|
if (origin === 'builtin') return h(doc, 'span', 'badge waiting grv-origin', 'built-in');
|
|
25
25
|
if (typeof origin === 'string' && origin.startsWith('plugin:')) return h(doc, 'span', 'badge violet grv-origin', origin);
|
|
26
|
+
// Team policy sets (gp:<id>): blue, like every policy origin; the title names the home.
|
|
27
|
+
if (typeof origin === 'string' && origin.startsWith('policy:')) {
|
|
28
|
+
const b = h(doc, 'span', 'badge blue grv-origin', 'policy');
|
|
29
|
+
b.title = `Shipped by the team policy on ${origin.slice(7)} — edit it on the Team policy page`;
|
|
30
|
+
return b;
|
|
31
|
+
}
|
|
26
32
|
return h(doc, 'span', 'badge green grv-origin', 'user');
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
/** Built-ins and team-policy sets are read-only here: View, Save as new set, never Delete. */
|
|
36
|
+
export function isReadOnlyGuardrailSet(s) {
|
|
37
|
+
return !!s && (s.origin === 'builtin' || (typeof s.origin === 'string' && s.origin.startsWith('policy:')));
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
// renderGuardrailList(sets) -> <div.grv-list> of cards; built-ins get View + no Delete.
|
|
30
41
|
export function renderGuardrailList(sets, { doc = globalThis.document } = {}) {
|
|
31
42
|
const root = h(doc, 'div', 'grv-list');
|
|
@@ -39,8 +50,9 @@ export function renderGuardrailList(sets, { doc = globalThis.document } = {}) {
|
|
|
39
50
|
body.appendChild(head);
|
|
40
51
|
body.appendChild(h(doc, 'small', 'grv-summary hint', guardrailSummary(s.settings)));
|
|
41
52
|
card.appendChild(body);
|
|
42
|
-
if (s
|
|
53
|
+
if (!isReadOnlyGuardrailSet(s)) {
|
|
43
54
|
const del = h(doc, 'button', 'btn-ghost grv-delete', 'Delete');
|
|
55
|
+
del.dataset.minLevel = 'expert'; // authoring sets is expert (docs/ui-levels.md)
|
|
44
56
|
del.type = 'button';
|
|
45
57
|
del.dataset.id = s.id;
|
|
46
58
|
card.appendChild(del);
|
|
@@ -52,7 +64,7 @@ export function renderGuardrailList(sets, { doc = globalThis.document } = {}) {
|
|
|
52
64
|
const open = h(doc, 'button', 'btn-ghost grv-edit grv-details', 'Details');
|
|
53
65
|
open.type = 'button';
|
|
54
66
|
open.dataset.id = s.id;
|
|
55
|
-
open.title = s
|
|
67
|
+
open.title = isReadOnlyGuardrailSet(s) ? 'View' : 'Edit';
|
|
56
68
|
card.appendChild(open);
|
|
57
69
|
root.appendChild(card);
|
|
58
70
|
}
|
|
@@ -172,7 +184,7 @@ export function renderGuardrailEditor(set, { doc = globalThis.document, mode = '
|
|
|
172
184
|
}
|
|
173
185
|
if (readOnly) {
|
|
174
186
|
head.appendChild(h(doc, 'b', 'grv-name', set.name));
|
|
175
|
-
head.appendChild(
|
|
187
|
+
head.appendChild(originBadge(doc, set.origin || 'builtin'));
|
|
176
188
|
} else {
|
|
177
189
|
const name = h(doc, 'input', 'input grv-name-input');
|
|
178
190
|
name.type = 'text';
|