open-managed-agents 0.1.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/LICENSE +93 -0
- package/README.md +248 -0
- package/bin/oma.mjs +499 -0
- package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
- package/dist/scripts/alpha-smoke-models.mjs +92 -0
- package/dist/scripts/alpha-smoke.mjs +581 -0
- package/dist/scripts/oma-doctor.js +230 -0
- package/dist/scripts/oma-models.js +299 -0
- package/dist/scripts/oma-workspaces.js +133 -0
- package/dist/src/control-plane/admin/auth.js +43 -0
- package/dist/src/control-plane/admin/routes.js +76 -0
- package/dist/src/control-plane/admin/service.js +102 -0
- package/dist/src/control-plane/admission.js +90 -0
- package/dist/src/control-plane/agents/routes.js +63 -0
- package/dist/src/control-plane/agents/service.js +687 -0
- package/dist/src/control-plane/agents/store.js +391 -0
- package/dist/src/control-plane/agents/types.js +1 -0
- package/dist/src/control-plane/api-constants.js +3 -0
- package/dist/src/control-plane/app.js +907 -0
- package/dist/src/control-plane/console/auth.js +68 -0
- package/dist/src/control-plane/console/static.js +112 -0
- package/dist/src/control-plane/deployment-runtime-config.js +328 -0
- package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
- package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
- package/dist/src/control-plane/deployment-storage.js +354 -0
- package/dist/src/control-plane/egress/image.js +48 -0
- package/dist/src/control-plane/egress/policy.js +571 -0
- package/dist/src/control-plane/egress/presets.js +77 -0
- package/dist/src/control-plane/egress/proxy.js +72 -0
- package/dist/src/control-plane/egress/ssrf.js +106 -0
- package/dist/src/control-plane/egress/utils/debug.js +14 -0
- package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
- package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
- package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
- package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
- package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
- package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
- package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
- package/dist/src/control-plane/environments/routes.js +54 -0
- package/dist/src/control-plane/environments/service.js +112 -0
- package/dist/src/control-plane/environments/store.js +132 -0
- package/dist/src/control-plane/environments/types.js +1 -0
- package/dist/src/control-plane/errors.js +81 -0
- package/dist/src/control-plane/events/broadcaster.js +217 -0
- package/dist/src/control-plane/events/constants.js +2 -0
- package/dist/src/control-plane/events/persist.js +39 -0
- package/dist/src/control-plane/events/request.js +190 -0
- package/dist/src/control-plane/events/routes.js +159 -0
- package/dist/src/control-plane/events/runtime-helpers.js +134 -0
- package/dist/src/control-plane/events/service.js +1985 -0
- package/dist/src/control-plane/events/session-guards.js +29 -0
- package/dist/src/control-plane/events/sse.js +6 -0
- package/dist/src/control-plane/events/store.js +772 -0
- package/dist/src/control-plane/events/tool-persistence.js +102 -0
- package/dist/src/control-plane/events/types.js +21 -0
- package/dist/src/control-plane/files/routes.js +121 -0
- package/dist/src/control-plane/files/service.js +65 -0
- package/dist/src/control-plane/files/store-common.js +87 -0
- package/dist/src/control-plane/files/store-local.js +488 -0
- package/dist/src/control-plane/files/store-memory.js +270 -0
- package/dist/src/control-plane/files/store.js +2 -0
- package/dist/src/control-plane/files/types.js +7 -0
- package/dist/src/control-plane/http.js +25 -0
- package/dist/src/control-plane/ids.js +43 -0
- package/dist/src/control-plane/logging.js +263 -0
- package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
- package/dist/src/control-plane/models/catalog.js +78 -0
- package/dist/src/control-plane/models/config-security.js +376 -0
- package/dist/src/control-plane/models/deployment-config.js +93 -0
- package/dist/src/control-plane/models/routes.js +32 -0
- package/dist/src/control-plane/models/service.js +138 -0
- package/dist/src/control-plane/observability/instruments.js +65 -0
- package/dist/src/control-plane/observability/metrics.js +125 -0
- package/dist/src/control-plane/observability/routes.js +95 -0
- package/dist/src/control-plane/openapi/document.js +282 -0
- package/dist/src/control-plane/openapi/routes.js +78 -0
- package/dist/src/control-plane/request-idempotency.js +85 -0
- package/dist/src/control-plane/secrets/envelope.js +132 -0
- package/dist/src/control-plane/secrets/master-key.js +44 -0
- package/dist/src/control-plane/secrets/routes.js +20 -0
- package/dist/src/control-plane/secrets/service.js +71 -0
- package/dist/src/control-plane/secrets/store.js +212 -0
- package/dist/src/control-plane/secrets/types.js +3 -0
- package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
- package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
- package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
- package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
- package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
- package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
- package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
- package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
- package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
- package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
- package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
- package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
- package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
- package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
- package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
- package/dist/src/control-plane/sessions/pi/translator.js +100 -0
- package/dist/src/control-plane/sessions/request.js +164 -0
- package/dist/src/control-plane/sessions/resources.js +63 -0
- package/dist/src/control-plane/sessions/routes.js +88 -0
- package/dist/src/control-plane/sessions/serialize.js +24 -0
- package/dist/src/control-plane/sessions/service.js +733 -0
- package/dist/src/control-plane/sessions/store.js +538 -0
- package/dist/src/control-plane/sessions/types.js +1 -0
- package/dist/src/control-plane/skills/archive.js +169 -0
- package/dist/src/control-plane/skills/routes.js +41 -0
- package/dist/src/control-plane/skills/service.js +29 -0
- package/dist/src/control-plane/skills/store.js +233 -0
- package/dist/src/control-plane/skills/types.js +5 -0
- package/dist/src/control-plane/sqlite-transaction.js +15 -0
- package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
- package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
- package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
- package/dist/src/control-plane/vaults/routes.js +66 -0
- package/dist/src/control-plane/vaults/service.js +553 -0
- package/dist/src/control-plane/vaults/store.js +691 -0
- package/dist/src/control-plane/vaults/types.js +1 -0
- package/dist/src/control-plane/wake-loop.js +65 -0
- package/dist/src/control-plane/wiring.js +92 -0
- package/dist/src/control-plane/workspace.js +4 -0
- package/dist/src/control-plane/workspaces/store.js +158 -0
- package/dist/src/egress-proxy-main.js +103 -0
- package/dist/src/main.js +124 -0
- package/dist/src/types/agents.js +1 -0
- package/dist/src/types/common.js +1 -0
- package/dist/src/types/environments.js +1 -0
- package/dist/src/types/events.js +121 -0
- package/dist/src/types/files.js +1 -0
- package/dist/src/types/json.js +19 -0
- package/dist/src/types/sessions.js +1 -0
- package/dist/ui/managed-agents-console/README.md +52 -0
- package/dist/ui/managed-agents-console/docs/agents.md +23 -0
- package/dist/ui/managed-agents-console/docs/console.md +23 -0
- package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
- package/dist/ui/managed-agents-console/docs/environments.md +27 -0
- package/dist/ui/managed-agents-console/docs/events.md +19 -0
- package/dist/ui/managed-agents-console/docs/files.md +17 -0
- package/dist/ui/managed-agents-console/docs/github.md +11 -0
- package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
- package/dist/ui/managed-agents-console/docs/memory.md +11 -0
- package/dist/ui/managed-agents-console/docs/migration.md +17 -0
- package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
- package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
- package/dist/ui/managed-agents-console/docs/overview.md +41 -0
- package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
- package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
- package/dist/ui/managed-agents-console/docs/reference.md +27 -0
- package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
- package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
- package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
- package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
- package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
- package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
- package/dist/ui/managed-agents-console/docs/skills.md +15 -0
- package/dist/ui/managed-agents-console/docs/tools.md +23 -0
- package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
- package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
- package/dist/ui/managed-agents-console/index.html +47 -0
- package/dist/ui/managed-agents-console/serve.mjs +116 -0
- package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
- package/dist/ui/managed-agents-console/src/api.js +1043 -0
- package/dist/ui/managed-agents-console/src/app.jsx +633 -0
- package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
- package/dist/ui/managed-agents-console/src/console.css +733 -0
- package/dist/ui/managed-agents-console/src/data.js +188 -0
- package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
- package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
- package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
- package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
- package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
- package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
- package/dist/ui/managed-agents-console/src/sse.js +185 -0
- package/dist/ui/managed-agents-console/src/states.jsx +90 -0
- package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
- package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
- package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
- package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
- package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
- package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
- package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
- package/dist/ui/openapi-docs/VENDOR.md +27 -0
- package/dist/ui/openapi-docs/index.html +16 -0
- package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
- package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
- package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
- package/package.json +53 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// forms.jsx — Create-session / create-agent modals → window
|
|
2
|
+
const { useState: useStateF } = React;
|
|
3
|
+
|
|
4
|
+
function Modal({ icon, title, sub, onClose, children, footer }) {
|
|
5
|
+
const stop = (e) => e.stopPropagation();
|
|
6
|
+
return (
|
|
7
|
+
<div className="overlay" onClick={onClose}>
|
|
8
|
+
<div className="modal" onClick={stop} role="dialog" aria-modal="true">
|
|
9
|
+
<div className="modal-head">
|
|
10
|
+
<div className="ic"><Icon name={icon} size={18} /></div>
|
|
11
|
+
<div>
|
|
12
|
+
<h2>{title}</h2>
|
|
13
|
+
{sub && <p>{sub}</p>}
|
|
14
|
+
</div>
|
|
15
|
+
<span className="kebab x" onClick={onClose}><Icon name="x" size={17} /></span>
|
|
16
|
+
</div>
|
|
17
|
+
<div className="modal-body scroll">{children}</div>
|
|
18
|
+
<div className="modal-foot">{footer}</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Labeled({ label, opt, hint, htmlFor, children }) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="form-row">
|
|
27
|
+
<label className="form-label" htmlFor={htmlFor}>{label}{opt && <span className="opt">optional</span>}</label>
|
|
28
|
+
{children}
|
|
29
|
+
{hint && <div className="field-hint">{hint}</div>}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─────────── Create session ───────────
|
|
35
|
+
function CreateSession({ agents = AGENTS, environments = ENVIRONMENTS, vaults = [], presetAgent, onClose, onCreate, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
36
|
+
const choices = agents.filter((a) => a.status === 'active');
|
|
37
|
+
const activeEnvironments = environments.filter((environment) => !environment.archived);
|
|
38
|
+
const activePreset = presetAgent?.status === 'active' ? presetAgent : null;
|
|
39
|
+
const [agentId, setAgentId] = useStateF((activePreset && activePreset.id) || (choices[0] && choices[0].id));
|
|
40
|
+
const [env, setEnv] = useStateF(() => apiMode === 'api'
|
|
41
|
+
? (activeEnvironments[0]?.id || '')
|
|
42
|
+
: (activeEnvironments[0]?.id || ENVIRONMENTS[0].id));
|
|
43
|
+
const [customEnv, setCustomEnv] = useStateF('');
|
|
44
|
+
const [title, setTitle] = useStateF('');
|
|
45
|
+
const [msg, setMsg] = useStateF('');
|
|
46
|
+
const [vaultIds, setVaultIds] = useStateF([]);
|
|
47
|
+
const [busy, setBusy] = useStateF(false);
|
|
48
|
+
const [error, setError] = useStateF('');
|
|
49
|
+
const [partial, setPartial] = useStateF(null);
|
|
50
|
+
const [sessionIntent] = useStateF(() => api?.createIdempotencyIntent?.());
|
|
51
|
+
const [eventIntent] = useStateF(() => api?.createIdempotencyIntent?.());
|
|
52
|
+
|
|
53
|
+
const agent = agents.find((a) => a.id === agentId);
|
|
54
|
+
const usingCustom = env === '__custom';
|
|
55
|
+
const finalEnv = usingCustom ? customEnv.trim() : env;
|
|
56
|
+
const valid = !!agentId && !!finalEnv && !busy;
|
|
57
|
+
const live = apiMode === 'api';
|
|
58
|
+
const initialMessageEvent = () => ({
|
|
59
|
+
type: 'user.message',
|
|
60
|
+
content: [{ type: 'text', text: msg.trim() }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const demoSession = () => ({
|
|
64
|
+
id:'sesn_01' + Math.random().toString(36).slice(2, 8) + '…new',
|
|
65
|
+
short:'sesn_…' + Math.random().toString(36).slice(2, 8),
|
|
66
|
+
title: title.trim() || (msg.trim() ? msg.trim().slice(0, 42) : 'Untitled session'),
|
|
67
|
+
status: msg.trim() ? 'running' : 'idle',
|
|
68
|
+
agent: agent.name, env: finalEnv,
|
|
69
|
+
created:'Just now', updated:'Just now', dur:'—', tokens:'0 / 0', resources:0,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const submit = async () => {
|
|
73
|
+
if (!valid) return;
|
|
74
|
+
setBusy(true);
|
|
75
|
+
setError('');
|
|
76
|
+
setPartial(null);
|
|
77
|
+
if (!live) {
|
|
78
|
+
onCreate(demoSession());
|
|
79
|
+
setBusy(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const body = {
|
|
83
|
+
agent: agentId,
|
|
84
|
+
environment_id: finalEnv,
|
|
85
|
+
...(vaultIds.length ? { vault_ids:vaultIds } : {}),
|
|
86
|
+
...(title.trim() ? { title: title.trim() } : {}),
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
const session = await api.createSession(body, {
|
|
90
|
+
intent: sessionIntent,
|
|
91
|
+
agentNames: new Map(agents.map((item) => [item.id, item.name])),
|
|
92
|
+
});
|
|
93
|
+
if (!msg.trim()) {
|
|
94
|
+
onCreate({ ...session, firstMessage: { status: 'not_sent' } });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const firstMessage = await api.sendSessionEvents(session.id, [initialMessageEvent()], {
|
|
99
|
+
intent: eventIntent,
|
|
100
|
+
});
|
|
101
|
+
onCreate({ ...session, status:'running', firstMessage: { status:'sent', response:firstMessage } });
|
|
102
|
+
} catch (messageError) {
|
|
103
|
+
setPartial({ session, error: messageError.message || 'First message failed.' });
|
|
104
|
+
setError(`Session ${session.short || session.id} was created, but the first message was not accepted: ${messageError.message || 'Request failed'}`);
|
|
105
|
+
if (messageError.status === 401 && onAuthExpired) onAuthExpired();
|
|
106
|
+
}
|
|
107
|
+
} catch (createError) {
|
|
108
|
+
setError(createError.message || 'Session creation failed.');
|
|
109
|
+
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
110
|
+
} finally {
|
|
111
|
+
setBusy(false);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const retryFirstMessage = async () => {
|
|
116
|
+
if (!partial || !msg.trim() || busy) return;
|
|
117
|
+
setBusy(true);
|
|
118
|
+
setError('');
|
|
119
|
+
try {
|
|
120
|
+
const firstMessage = await api.sendSessionEvents(partial.session.id, [initialMessageEvent()], {
|
|
121
|
+
intent: eventIntent,
|
|
122
|
+
});
|
|
123
|
+
onCreate({ ...partial.session, status:'running', firstMessage: { status:'sent_after_retry', response:firstMessage } });
|
|
124
|
+
} catch (messageError) {
|
|
125
|
+
setError(`Session ${partial.session.short || partial.session.id} exists, but the first message retry failed: ${messageError.message || 'Request failed'}`);
|
|
126
|
+
if (messageError.status === 401 && onAuthExpired) onAuthExpired();
|
|
127
|
+
} finally {
|
|
128
|
+
setBusy(false);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Modal icon="activity" title="Create session" sub="Start a new Managed Agents session against a local environment." onClose={onClose}
|
|
134
|
+
footer={<>
|
|
135
|
+
<span className="left">Sends to <span className="mono">POST /v1/sessions</span>{msg.trim() && <> then <span className="mono">POST /v1/sessions/:id/events</span></>}</span>
|
|
136
|
+
<button className="btn" onClick={onClose}>{partial ? 'Close' : 'Cancel'}</button>
|
|
137
|
+
{partial && <button className="btn" disabled={busy} onClick={() => onCreate(partial.session)}>Open session</button>}
|
|
138
|
+
{partial && msg.trim() && <button className="btn btn-primary" disabled={busy} onClick={retryFirstMessage} style={{ opacity: busy ? .5 : 1 }}>
|
|
139
|
+
<Icon name="send" size={15} />Retry message</button>}
|
|
140
|
+
{!partial && <button className="btn btn-primary" disabled={!valid} onClick={submit} style={{ opacity: valid ? 1 : .5 }}>
|
|
141
|
+
<Icon name="plus" size={15} />{busy ? 'Creating…' : 'Create session'}</button>}
|
|
142
|
+
</>}>
|
|
143
|
+
<Labeled label="Agent" htmlFor="create-session-agent">
|
|
144
|
+
<select id="create-session-agent" name="agent" className="selectbox" value={agentId} onChange={(e) => setAgentId(e.target.value)}>
|
|
145
|
+
{choices.map((a) => (
|
|
146
|
+
<option key={a.id} value={a.id}>{a.name} · {a.model}</option>
|
|
147
|
+
))}
|
|
148
|
+
</select>
|
|
149
|
+
{agent && <div className="field-hint">{agent.short} · system prompt {agent.system.length} chars · {agent.tools} tools</div>}
|
|
150
|
+
</Labeled>
|
|
151
|
+
|
|
152
|
+
<Labeled label="Environment" htmlFor="create-session-environment" hint="Pick an existing environment or enter an ID manually.">
|
|
153
|
+
<select id="create-session-environment" name="environment" className="selectbox" value={env} onChange={(e) => setEnv(e.target.value)}>
|
|
154
|
+
{live && activeEnvironments.length === 0 && <option value="" disabled>Create an active environment first…</option>}
|
|
155
|
+
{activeEnvironments.map((en) => <option key={en.id} value={en.id}>{en.label} — {en.image}</option>)}
|
|
156
|
+
<option value="__custom">Enter an environment ID manually…</option>
|
|
157
|
+
</select>
|
|
158
|
+
</Labeled>
|
|
159
|
+
{live && activeEnvironments.length === 0 && !usingCustom && (
|
|
160
|
+
<div className="inline-warn" role="status"><Icon name="alert" size={14} /><span>No active live environments exist. Close this dialog and create one, or select “Enter an environment ID manually”.</span></div>
|
|
161
|
+
)}
|
|
162
|
+
{usingCustom && (
|
|
163
|
+
<Labeled label="Environment ID" htmlFor="create-session-environment-id">
|
|
164
|
+
<input id="create-session-environment-id" name="environment_id" className="input mono" placeholder="env_…" value={customEnv} onChange={(e) => setCustomEnv(e.target.value)} />
|
|
165
|
+
</Labeled>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
<Labeled label="Credential vaults" opt hint="Vaults are attached to this session. Credentials remain write-only and are matched to the agent's MCP server URLs at runtime.">
|
|
169
|
+
{vaults.length === 0 ? <div className="field-hint">No active vaults in this workspace.</div>
|
|
170
|
+
: <div className="seg-tools">{vaults.filter((vault) => !vault.archived_at).map((vault) => {
|
|
171
|
+
const selected = vaultIds.includes(vault.id);
|
|
172
|
+
return <button type="button" key={vault.id} className={'tool-toggle' + (selected ? ' on' : '')} aria-pressed={selected}
|
|
173
|
+
onClick={() => setVaultIds((current) => selected ? current.filter((id) => id !== vault.id) : [...current, vault.id])}>
|
|
174
|
+
<span className="chk">{selected && <Icon name="checkCircle" size={9} />}</span><span>{vault.display_name}</span>
|
|
175
|
+
</button>;
|
|
176
|
+
})}</div>}
|
|
177
|
+
</Labeled>
|
|
178
|
+
|
|
179
|
+
<Labeled label="Title" htmlFor="create-session-title" opt hint="Defaults to the first message if left blank.">
|
|
180
|
+
<input id="create-session-title" name="title" className="input" placeholder="e.g. Ship your first Managed Agent" value={title} onChange={(e) => setTitle(e.target.value)} />
|
|
181
|
+
</Labeled>
|
|
182
|
+
|
|
183
|
+
<Labeled label="First message" htmlFor="create-session-message" opt hint="Send an initial user.message — the session starts running immediately.">
|
|
184
|
+
<textarea id="create-session-message" name="message" className="textarea" placeholder="Send a message to start the session…" value={msg} onChange={(e) => setMsg(e.target.value)} />
|
|
185
|
+
</Labeled>
|
|
186
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
187
|
+
</Modal>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─────────── Create agent ───────────
|
|
192
|
+
function CreateVaultModal({ onClose, onCreated, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
193
|
+
const [displayName, setDisplayName] = useStateF('');
|
|
194
|
+
const [busy, setBusy] = useStateF(false);
|
|
195
|
+
const [error, setError] = useStateF('');
|
|
196
|
+
const submit = async () => {
|
|
197
|
+
if (!displayName.trim() || busy) return;
|
|
198
|
+
setBusy(true); setError('');
|
|
199
|
+
try {
|
|
200
|
+
if (apiMode !== 'api') { onCreated({ id:`vlt_demo_${Date.now()}`, display_name:displayName.trim() }); return; }
|
|
201
|
+
onCreated(await api.createVault({ display_name:displayName.trim() }));
|
|
202
|
+
} catch (createError) {
|
|
203
|
+
setError(createError.message || 'Vault creation failed.');
|
|
204
|
+
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
205
|
+
} finally { setBusy(false); }
|
|
206
|
+
};
|
|
207
|
+
return <Modal icon="database" title="Create vault" sub="Create a workspace-scoped container for credentials." onClose={onClose}
|
|
208
|
+
footer={<><span className="left">Sends to <span className="mono">POST /v1/vaults</span></span><button className="btn" onClick={onClose}>Cancel</button><button className="btn btn-primary" disabled={!displayName.trim() || busy} onClick={submit}>{busy ? 'Creating…' : 'Create vault'}</button></>}>
|
|
209
|
+
<Labeled label="Vault name" htmlFor="create-vault-name" hint="Names are visible in the console; credential values are never shown again."><input id="create-vault-name" className="input" autoFocus value={displayName} onChange={(event) => setDisplayName(event.target.value)} /></Labeled>
|
|
210
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
211
|
+
</Modal>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function CreateVaultCredentialModal({ vault, onClose, onCreated, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
215
|
+
const [displayName, setDisplayName] = useStateF('');
|
|
216
|
+
const [type, setType] = useStateF('static_bearer');
|
|
217
|
+
const [serverUrl, setServerUrl] = useStateF('');
|
|
218
|
+
const [token, setToken] = useStateF('');
|
|
219
|
+
const [expiresAt, setExpiresAt] = useStateF('');
|
|
220
|
+
const [busy, setBusy] = useStateF(false);
|
|
221
|
+
const [error, setError] = useStateF('');
|
|
222
|
+
const valid = /^https?:\/\//.test(serverUrl.trim()) && token.trim().length >= 8 && !busy;
|
|
223
|
+
const submit = async () => {
|
|
224
|
+
if (!valid) return;
|
|
225
|
+
setBusy(true); setError('');
|
|
226
|
+
const auth = type === 'static_bearer'
|
|
227
|
+
? { type, mcp_server_url:serverUrl.trim(), token:token.trim() }
|
|
228
|
+
: { type, mcp_server_url:serverUrl.trim(), access_token:token.trim(), ...(expiresAt.trim() ? { expires_at:expiresAt.trim() } : {}) };
|
|
229
|
+
try {
|
|
230
|
+
if (apiMode !== 'api') { onCreated({ id:`vcrd_demo_${Date.now()}`, vault_id:vault.id, display_name:displayName.trim() || null, auth }); return; }
|
|
231
|
+
onCreated(await api.createVaultCredential(vault.id, { ...(displayName.trim() ? { display_name:displayName.trim() } : {}), auth }));
|
|
232
|
+
} catch (createError) {
|
|
233
|
+
setError(createError.message || 'Credential creation failed.');
|
|
234
|
+
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
235
|
+
} finally { setBusy(false); }
|
|
236
|
+
};
|
|
237
|
+
return <Modal icon="database" title="Add credential" sub={`Write-only credential for ${vault.display_name || vault.displayName}.`} onClose={onClose}
|
|
238
|
+
footer={<><span className="left">Sends to <span className="mono">POST /v1/vaults/:id/credentials</span></span><button className="btn" onClick={onClose}>Cancel</button><button className="btn btn-primary" disabled={!valid} onClick={submit}>{busy ? 'Saving…' : 'Save credential'}</button></>}>
|
|
239
|
+
<Labeled label="Credential name" htmlFor="credential-name" opt><input id="credential-name" className="input" value={displayName} onChange={(event) => setDisplayName(event.target.value)} /></Labeled>
|
|
240
|
+
<Labeled label="Credential type" htmlFor="credential-type"><select id="credential-type" className="selectbox" value={type} onChange={(event) => setType(event.target.value)}><option value="static_bearer">Static bearer token</option><option value="mcp_oauth">MCP OAuth access token</option></select></Labeled>
|
|
241
|
+
<Labeled label="MCP server URL" htmlFor="credential-server-url" hint="This must exactly match the URL configured on the agent."><input id="credential-server-url" className="input mono" placeholder="https://mcp.example.com/mcp" value={serverUrl} onChange={(event) => setServerUrl(event.target.value)} /></Labeled>
|
|
242
|
+
<Labeled label={type === 'static_bearer' ? 'Bearer token' : 'OAuth access token'} htmlFor="credential-token" hint="Write-only. OMA never returns or renders this value after it is submitted."><input id="credential-token" type="password" className="input mono" autoComplete="off" value={token} onChange={(event) => setToken(event.target.value)} /></Labeled>
|
|
243
|
+
{type === 'mcp_oauth' && <Labeled label="Expires at" htmlFor="credential-expires" opt hint="ISO-8601 timestamp. Leave blank only for a non-expiring access token."><input id="credential-expires" className="input mono" placeholder="2027-01-01T00:00:00Z" value={expiresAt} onChange={(event) => setExpiresAt(event.target.value)} /></Labeled>}
|
|
244
|
+
{type === 'mcp_oauth' && <div className="field-hint">Use the API for OAuth refresh-token and client-secret configuration; this initial console path safely supports access-token credentials and validation.</div>}
|
|
245
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
246
|
+
</Modal>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function CreateSkillModal({ onClose, onCreated, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
250
|
+
const [title, setTitle] = useStateF('');
|
|
251
|
+
const [files, setFiles] = useStateF([]);
|
|
252
|
+
const [busy, setBusy] = useStateF(false);
|
|
253
|
+
const [error, setError] = useStateF('');
|
|
254
|
+
const valid = files.length > 0 && !busy;
|
|
255
|
+
const submit = async () => {
|
|
256
|
+
if (!valid) return;
|
|
257
|
+
setBusy(true); setError('');
|
|
258
|
+
try {
|
|
259
|
+
if (apiMode !== 'api') { onCreated({ id:`skill_demo_${Date.now()}`, display_title:title || 'Demo skill', latest_version:'1' }); return; }
|
|
260
|
+
onCreated(await api.createSkill(title.trim(), files));
|
|
261
|
+
} catch (createError) {
|
|
262
|
+
setError(createError.message || 'Skill upload failed.');
|
|
263
|
+
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
264
|
+
} finally { setBusy(false); }
|
|
265
|
+
};
|
|
266
|
+
return <Modal icon="fileText" title="Upload custom skill" sub="Upload a .zip bundle containing SKILL.md and any supporting files." onClose={onClose}
|
|
267
|
+
footer={<><span className="left">Sends multipart data to <span className="mono">POST /v1/skills</span></span><button className="btn" onClick={onClose}>Cancel</button><button className="btn btn-primary" disabled={!valid} onClick={submit}>{busy ? 'Uploading…' : 'Upload skill'}</button></>}>
|
|
268
|
+
<Labeled label="Display title" htmlFor="skill-title" opt><input id="skill-title" className="input" placeholder="Defaults to the skill manifest title" value={title} onChange={(event) => setTitle(event.target.value)} /></Labeled>
|
|
269
|
+
<Labeled label="Skill bundle" htmlFor="skill-files" hint="Select one .zip with a single top-level folder containing SKILL.md. The API validates the bundle and limits each request to 30 MB."><input id="skill-files" type="file" className="input" accept=".zip,application/zip" onChange={(event) => setFiles([...event.target.files])} />{files.length > 0 && <div className="field-hint">{files.map((file) => file.name).join(', ')}</div>}</Labeled>
|
|
270
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
271
|
+
</Modal>;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function CreateAgent({ models = [], skills = [], onClose, onCreate, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
275
|
+
const defaultModel = models.find((item) => item.default) || models[0] || null;
|
|
276
|
+
const [name, setName] = useStateF('');
|
|
277
|
+
const [provider, setProvider] = useStateF(defaultModel?.provider || '');
|
|
278
|
+
const [model, setModel] = useStateF(defaultModel?.id || '');
|
|
279
|
+
const [prompt, setPrompt] = useStateF('');
|
|
280
|
+
const [description, setDescription] = useStateF('');
|
|
281
|
+
const [mcpName, setMcpName] = useStateF('');
|
|
282
|
+
const [mcpUrl, setMcpUrl] = useStateF('');
|
|
283
|
+
const [skillIds, setSkillIds] = useStateF([]);
|
|
284
|
+
const [metadata, setMetadata] = useStateF('');
|
|
285
|
+
const [tools, setTools] = useStateF(['bash']);
|
|
286
|
+
const [toolPolicy, setToolPolicy] = useStateF('always_ask');
|
|
287
|
+
const [busy, setBusy] = useStateF(false);
|
|
288
|
+
const [error, setError] = useStateF('');
|
|
289
|
+
|
|
290
|
+
const live = apiMode === 'api';
|
|
291
|
+
const providers = [...new Set(models.map((item) => item.provider))];
|
|
292
|
+
const providerModels = models.filter((item) => item.provider === provider);
|
|
293
|
+
const selectedModel = providerModels.find((item) => item.id === model) || null;
|
|
294
|
+
const hasMcp = mcpName.trim() || mcpUrl.trim();
|
|
295
|
+
const mcpValid = !hasMcp || (mcpName.trim().length > 0 && /^https?:\/\//.test(mcpUrl.trim()));
|
|
296
|
+
const valid = name.trim().length > 0 && selectedModel !== null && mcpValid && !busy;
|
|
297
|
+
const toggle = (t) => setTools(tools.includes(t) ? tools.filter((x) => x !== t) : [...tools, t]);
|
|
298
|
+
const chooseProvider = (nextProvider) => {
|
|
299
|
+
setProvider(nextProvider);
|
|
300
|
+
const choices = models.filter((item) => item.provider === nextProvider);
|
|
301
|
+
const nextModel = choices.find((item) => item.default) || choices[0] || null;
|
|
302
|
+
setModel(nextModel?.id || '');
|
|
303
|
+
};
|
|
304
|
+
const chooseSkills = (event) => {
|
|
305
|
+
const nextSkills = [...event.target.selectedOptions].map((option) => option.value);
|
|
306
|
+
setSkillIds(nextSkills);
|
|
307
|
+
if (nextSkills.length > 0) setTools((current) => current.includes('read') ? current : [...current, 'read']);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const demoAgent = () => ({
|
|
311
|
+
id:'agent_01' + Math.random().toString(36).slice(2, 8) + '…new',
|
|
312
|
+
short:'agent_…' + Math.random().toString(36).slice(2, 8),
|
|
313
|
+
name: name.trim(), model:`${provider}/${model}`, modelProvider:provider, modelId:model,
|
|
314
|
+
status:'active', created:'Just now', updated:'Just now', version:'v1',
|
|
315
|
+
tools: tools.length + (hasMcp ? 1 : 0), system: prompt.trim() || 'No system prompt set.', description:description.trim() || null,
|
|
316
|
+
mcpServers: hasMcp ? [{ name:mcpName.trim(), url:mcpUrl.trim() }] : [], skills:skillIds.map((id) => ({ skill_id:id })), metadata:{},
|
|
317
|
+
toolset:'agent_toolset_20260401',
|
|
318
|
+
toolPermission:toolPolicy === 'always_allow' ? 'Allow automatically' : 'Ask before use',
|
|
319
|
+
sessions:[],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const submit = async () => {
|
|
323
|
+
if (!valid) return;
|
|
324
|
+
setBusy(true);
|
|
325
|
+
setError('');
|
|
326
|
+
if (!live) {
|
|
327
|
+
onCreate(demoAgent());
|
|
328
|
+
setBusy(false);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
let parsedMetadata;
|
|
332
|
+
try {
|
|
333
|
+
parsedMetadata = metadata.trim() ? JSON.parse(metadata) : null;
|
|
334
|
+
if (parsedMetadata !== null && (Array.isArray(parsedMetadata) || typeof parsedMetadata !== 'object' || Object.values(parsedMetadata).some((value) => typeof value !== 'string'))) {
|
|
335
|
+
throw new Error('Metadata must be a JSON object with string values.');
|
|
336
|
+
}
|
|
337
|
+
} catch (metadataError) {
|
|
338
|
+
setError(metadataError.message || 'Metadata must be valid JSON.');
|
|
339
|
+
setBusy(false);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const mcpServers = hasMcp ? [{ type:'url', name:mcpName.trim(), url:mcpUrl.trim() }] : [];
|
|
343
|
+
const body = {
|
|
344
|
+
name: name.trim(),
|
|
345
|
+
model: api.modelInputForSelection(selectedModel),
|
|
346
|
+
...(prompt.trim() ? { system: prompt.trim() } : {}),
|
|
347
|
+
...(description.trim() ? { description:description.trim() } : {}),
|
|
348
|
+
...(parsedMetadata ? { metadata:parsedMetadata } : {}),
|
|
349
|
+
...(mcpServers.length ? { mcp_servers:mcpServers } : {}),
|
|
350
|
+
...(skillIds.length ? { skills:skillIds.map((skillId) => ({ type:'custom', skill_id:skillId })) } : {}),
|
|
351
|
+
tools: [{
|
|
352
|
+
type: 'agent_toolset_20260401',
|
|
353
|
+
configs: TOOL_OPTIONS.map((tool) => ({
|
|
354
|
+
name:tool,
|
|
355
|
+
enabled:tools.includes(tool),
|
|
356
|
+
...(tools.includes(tool) ? { permission_policy:{ type:toolPolicy } } : {}),
|
|
357
|
+
})),
|
|
358
|
+
}, ...mcpServers.map((server) => ({
|
|
359
|
+
type:'mcp_toolset',
|
|
360
|
+
mcp_server_name:server.name,
|
|
361
|
+
default_config:{ enabled:true, permission_policy:{ type:toolPolicy } },
|
|
362
|
+
}))],
|
|
363
|
+
};
|
|
364
|
+
try {
|
|
365
|
+
const agent = await api.createAgent(body);
|
|
366
|
+
onCreate(agent);
|
|
367
|
+
} catch (createError) {
|
|
368
|
+
setError(createError.message || 'Agent creation failed. Refresh the agent list before retrying if the request may have reached the server.');
|
|
369
|
+
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
370
|
+
} finally {
|
|
371
|
+
setBusy(false);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<Modal icon="bot" title="Create agent" sub="Define a reusable agent template — model, prompt, and tools." onClose={onClose}
|
|
377
|
+
footer={<>
|
|
378
|
+
<span className="left">Sends to <span className="mono">POST /v1/agents</span></span>
|
|
379
|
+
<button className="btn" onClick={onClose}>Cancel</button>
|
|
380
|
+
<button className="btn btn-primary" disabled={!valid} onClick={submit} style={{ opacity: valid ? 1 : .5 }}>
|
|
381
|
+
<Icon name="plus" size={15} />{busy ? 'Creating…' : 'Create agent'}</button>
|
|
382
|
+
</>}>
|
|
383
|
+
<div className="form-row two">
|
|
384
|
+
<Labeled label="Name" htmlFor="create-agent-name">
|
|
385
|
+
<input id="create-agent-name" name="name" className="input" placeholder="e.g. cwc-agent" value={name} onChange={(e) => setName(e.target.value)} />
|
|
386
|
+
</Labeled>
|
|
387
|
+
<Labeled label="Provider" htmlFor="create-agent-provider">
|
|
388
|
+
<select id="create-agent-provider" name="provider" className="selectbox mono" value={provider} onChange={(e) => chooseProvider(e.target.value)}>
|
|
389
|
+
{providers.map((item) => <option key={item} value={item}>{item}</option>)}
|
|
390
|
+
</select>
|
|
391
|
+
</Labeled>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<Labeled label="Model" htmlFor="create-agent-model" hint="Search the deployment-enabled Pi catalog for this provider.">
|
|
395
|
+
<input id="create-agent-model" name="model" className="input mono" list="oma-model-suggestions" value={model} onChange={(e) => setModel(e.target.value)} />
|
|
396
|
+
<datalist id="oma-model-suggestions">{providerModels.map((item) => <option key={`${item.provider}/${item.id}`} value={item.id}>{item.name}</option>)}</datalist>
|
|
397
|
+
</Labeled>
|
|
398
|
+
{selectedModel && <div className={selectedModel.credentials_configured ? 'field-hint' : 'inline-warn'} role={selectedModel.credentials_configured ? undefined : 'status'}>
|
|
399
|
+
{!selectedModel.credentials_configured && <Icon name="alert" size={14} />}
|
|
400
|
+
<span>{selectedModel.credentials_configured
|
|
401
|
+
? `${selectedModel.provider_name || selectedModel.provider} credentials are configured on this appliance.`
|
|
402
|
+
: `Credentials are not configured for ${selectedModel.provider}/${selectedModel.id}. You can create the agent, but sessions will be rejected until the operator runs oma auth set ${selectedModel.provider} and restarts oma up.`}</span>
|
|
403
|
+
</div>}
|
|
404
|
+
{live && models.length === 0 && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>No deployment-enabled models were returned. Agent creation is unavailable.</span></div>}
|
|
405
|
+
|
|
406
|
+
<Labeled label="System prompt" htmlFor="create-agent-system" opt hint="Plain text. Markdown is preserved.">
|
|
407
|
+
<textarea id="create-agent-system" name="system" className="textarea" placeholder="You help me navigate…" value={prompt} onChange={(e) => setPrompt(e.target.value)} />
|
|
408
|
+
</Labeled>
|
|
409
|
+
|
|
410
|
+
<Labeled label="Description" htmlFor="create-agent-description" opt hint="A short, user-facing summary of what this agent is for.">
|
|
411
|
+
<input id="create-agent-description" name="description" className="input" value={description} onChange={(e) => setDescription(e.target.value)} />
|
|
412
|
+
</Labeled>
|
|
413
|
+
|
|
414
|
+
<Labeled label="MCP server" opt hint="Declare one URL-based MCP server. A matching MCP toolset is created automatically; attach credentials through a vault when starting a session.">
|
|
415
|
+
<div className="form-row two"><input className="input" placeholder="Name, e.g. notion" value={mcpName} onChange={(e) => setMcpName(e.target.value)} />
|
|
416
|
+
<input className="input mono" placeholder="https://mcp.example.com/mcp" value={mcpUrl} onChange={(e) => setMcpUrl(e.target.value)} /></div>
|
|
417
|
+
{hasMcp && !mcpValid && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>Provide both an MCP name and an http(s) URL.</span></div>}
|
|
418
|
+
</Labeled>
|
|
419
|
+
|
|
420
|
+
<Labeled label="Custom skills" opt hint="Attach uploaded workspace skills to this immutable agent version.">
|
|
421
|
+
{skills.length === 0 ? <div className="field-hint">No custom skills uploaded yet.</div>
|
|
422
|
+
: <select className="selectbox" multiple value={skillIds} onChange={chooseSkills}>
|
|
423
|
+
{skills.map((skill) => <option key={skill.id} value={skill.id}>{skill.display_title}{skill.latest_version ? ` · ${skill.latest_version}` : ''}</option>)}
|
|
424
|
+
</select>}
|
|
425
|
+
{skillIds.length > 0 && <div className="field-hint">The <span className="mono">read</span> tool is enabled automatically because custom skills require it at session admission.</div>}
|
|
426
|
+
</Labeled>
|
|
427
|
+
|
|
428
|
+
<Labeled label="Metadata" htmlFor="create-agent-metadata" opt hint="Advanced: JSON object with string values. Stored with the agent version.">
|
|
429
|
+
<textarea id="create-agent-metadata" className="textarea mono" placeholder={'{"template":"support-agent"}'} value={metadata} onChange={(e) => setMetadata(e.target.value)} />
|
|
430
|
+
</Labeled>
|
|
431
|
+
|
|
432
|
+
<Labeled label="Built-in tools" hint="Choose which sandbox-backed tools the agent may use.">
|
|
433
|
+
<div className="seg-tools">
|
|
434
|
+
{TOOL_OPTIONS.map((t) => {
|
|
435
|
+
const on = tools.includes(t);
|
|
436
|
+
const requiredForSkills = skillIds.length > 0 && t === 'read';
|
|
437
|
+
return (
|
|
438
|
+
<button type="button" key={t} className={'tool-toggle' + (on ? ' on' : '')}
|
|
439
|
+
aria-pressed={on} disabled={requiredForSkills} title={requiredForSkills ? 'Custom skills require the read tool.' : undefined} onClick={() => toggle(t)}>
|
|
440
|
+
<span className="chk">{on && <Icon name="checkCircle" size={9} />}</span>
|
|
441
|
+
<span className="mono">{t}</span>
|
|
442
|
+
</button>
|
|
443
|
+
);
|
|
444
|
+
})}
|
|
445
|
+
</div>
|
|
446
|
+
</Labeled>
|
|
447
|
+
<Labeled label="Tool approval" hint="This applies to every enabled built-in tool on this agent.">
|
|
448
|
+
<div className="env-preset-grid" role="radiogroup" aria-label="Tool approval policy">
|
|
449
|
+
<button type="button" className={'env-choice' + (toolPolicy === 'always_ask' ? ' on' : '')}
|
|
450
|
+
role="radio" aria-checked={toolPolicy === 'always_ask'} disabled={busy}
|
|
451
|
+
onClick={() => setToolPolicy('always_ask')}>
|
|
452
|
+
<span className="dot-r" />
|
|
453
|
+
<span><span className="r-main">Ask before use</span><span className="r-sub">Show Allow/Deny before each tool call.</span></span>
|
|
454
|
+
</button>
|
|
455
|
+
<button type="button" className={'env-choice' + (toolPolicy === 'always_allow' ? ' on' : '')}
|
|
456
|
+
role="radio" aria-checked={toolPolicy === 'always_allow'} disabled={busy}
|
|
457
|
+
onClick={() => setToolPolicy('always_allow')}>
|
|
458
|
+
<span className="dot-r" />
|
|
459
|
+
<span><span className="r-main">Allow automatically</span><span className="r-sub">No confirmation prompts. Sandbox and network restrictions still apply.</span></span>
|
|
460
|
+
</button>
|
|
461
|
+
</div>
|
|
462
|
+
{toolPolicy === 'always_allow' && <div className="inline-warn" role="status"><Icon name="alert" size={14} /><span>The model may run every enabled tool without asking. Use this for trusted, disposable coding sessions.</span></div>}
|
|
463
|
+
</Labeled>
|
|
464
|
+
{live && <div className="field-hint">Only the currently supported sandbox-backed tools are offered. Disabled tools are persisted explicitly.</div>}
|
|
465
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
466
|
+
</Modal>
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
Object.assign(window, { Modal, CreateSession, CreateAgent, ConfirmDialog });
|
|
471
|
+
|
|
472
|
+
// ─────────── Archive / delete confirmation ───────────
|
|
473
|
+
function ConfirmDialog({ icon = 'archive', danger, title, message, confirmLabel, endpoint, onClose, onConfirm, busy = false, error = null }) {
|
|
474
|
+
return (
|
|
475
|
+
<div className="overlay" onClick={() => { if (!busy) onClose(); }}>
|
|
476
|
+
<div className="modal sm" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true">
|
|
477
|
+
<div className="modal-head">
|
|
478
|
+
<div className={'ic' + (danger ? ' danger' : '')}><Icon name={icon} size={18} /></div>
|
|
479
|
+
<div><h2>{title}</h2></div>
|
|
480
|
+
<span className="kebab x" onClick={() => { if (!busy) onClose(); }}><Icon name="x" size={17} /></span>
|
|
481
|
+
</div>
|
|
482
|
+
<div className="modal-body">
|
|
483
|
+
<div className="confirm-msg">{message}</div>
|
|
484
|
+
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error.message || String(error)}</span></div>}
|
|
485
|
+
</div>
|
|
486
|
+
<div className="modal-foot">
|
|
487
|
+
{endpoint && <span className="left">Sends to <span className="mono">{endpoint}</span></span>}
|
|
488
|
+
<button className="btn" disabled={busy} onClick={onClose}>Cancel</button>
|
|
489
|
+
<button className={'btn ' + (danger ? 'btn-danger' : 'btn-primary')} disabled={busy} onClick={onConfirm}>{busy ? 'Working…' : confirmLabel}</button>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// icons.jsx — compact Lucide-style icon set (stroke, 24 viewBox)
|
|
2
|
+
const ICON_PATHS = {
|
|
3
|
+
activity: 'M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2',
|
|
4
|
+
bot: 'M12 8V4H8 M4 8h12a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2Z M2 14h.01 M18 14h.01 M9 13v2 M15 13v2',
|
|
5
|
+
folder: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z',
|
|
6
|
+
search: 'M21 21l-4.34-4.34 M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z',
|
|
7
|
+
plus: 'M5 12h14 M12 5v14',
|
|
8
|
+
chevDown: 'M6 9l6 6 6-6',
|
|
9
|
+
chevRight: 'M9 6l6 6-6 6',
|
|
10
|
+
chevLeft: 'M15 6l-6 6 6 6',
|
|
11
|
+
clock: 'M12 6v6l4 2 M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20Z',
|
|
12
|
+
layers: 'M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z M2 12.5l8.58 3.9a2 2 0 0 0 1.66 0L21 12.5 M2 17l8.58 3.9a2 2 0 0 0 1.66 0L21 17',
|
|
13
|
+
download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4 M7 10l5 5 5-5 M12 15V3',
|
|
14
|
+
play: 'M6 3l14 9-14 9Z',
|
|
15
|
+
stop: 'M9 9h6v6H9z M5 5h14v14H5z',
|
|
16
|
+
x: 'M18 6L6 18 M6 6l12 12',
|
|
17
|
+
more: 'M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z M12 6a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z M12 20a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z',
|
|
18
|
+
terminal: 'M4 17l6-6-6-6 M12 19h8',
|
|
19
|
+
filter: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z',
|
|
20
|
+
arrowRight: 'M5 12h14 M12 5l7 7-7 7',
|
|
21
|
+
checkCircle: 'M22 11.08V12a10 10 0 1 1-5.93-9.14 M22 4L12 14.01l-3-3',
|
|
22
|
+
fileText: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8',
|
|
23
|
+
sparkles: 'M9.94 6.5l-1.7 3.7-3.7 1.7 3.7 1.7 1.7 3.7 1.7-3.7 3.7-1.7-3.7-1.7zM18 4l-.8 1.8-1.8.8 1.8.8.8 1.8.8-1.8 1.8-.8-1.8-.8zM18 16l-.6 1.4-1.4.6 1.4.6.6 1.4.6-1.4 1.4-.6-1.4-.6z',
|
|
24
|
+
archive: 'M21 8v13H3V8 M1 3h22v5H1z M10 12h4',
|
|
25
|
+
trash: 'M3 6h18 M8 6V4h8v2 M19 6l-1 15H6L5 6 M10 11v5 M14 11v5',
|
|
26
|
+
alert: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z M12 9v4 M12 17h.01',
|
|
27
|
+
info: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20Z M12 10v6 M12 7h.01',
|
|
28
|
+
panelLeft: 'M3 3h18v18H3z M9 3v18',
|
|
29
|
+
copy: 'M20 9H11a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2z M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1',
|
|
30
|
+
refresh: 'M3 12a9 9 0 0 1 15-6.7L21 8 M21 3v5h-5 M21 12a9 9 0 0 1-15 6.7L3 16 M3 21v-5h5',
|
|
31
|
+
cpu: 'M9 9h6v6H9z M4 7h2 M4 12h2 M4 17h2 M18 7h2 M18 12h2 M18 17h2 M7 4v2 M12 4v2 M17 4v2 M7 18v2 M12 18v2 M17 18v2 M6 6h12v12H6z',
|
|
32
|
+
zap: 'M13 2L3 14h9l-1 8 10-12h-9l1-8z',
|
|
33
|
+
hash: 'M4 9h16 M4 15h16 M10 3L8 21 M16 3l-2 18',
|
|
34
|
+
gitBranch: 'M6 3v12 M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z M6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z M15 6a9 9 0 0 1-9 9',
|
|
35
|
+
database: 'M12 8c4.97 0 9-1.34 9-3s-4.03-3-9-3-9 1.34-9 3 4.03 3 9 3z M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5 M3 12c0 1.66 4 3 9 3s9-1.34 9-3',
|
|
36
|
+
send: 'M14.54 9.46L4 14l16 6-6-16-4.54 10.54z M14.54 9.46L20 4',
|
|
37
|
+
};
|
|
38
|
+
function Icon({ name, size = 16, sw = 1.75, style, className }) {
|
|
39
|
+
const d = ICON_PATHS[name];
|
|
40
|
+
return (
|
|
41
|
+
<svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none"
|
|
42
|
+
stroke="currentColor" strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round"
|
|
43
|
+
style={{ flex:'0 0 auto', ...style }}>
|
|
44
|
+
{d.split(' M').map((seg, i) => <path key={i} d={(i ? 'M' : '') + seg} />)}
|
|
45
|
+
</svg>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
window.Icon = Icon;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// skills.jsx — workspace custom-skill inventory. Skill bundles are uploaded
|
|
2
|
+
// separately, then attached to an immutable agent version during authoring.
|
|
3
|
+
function SkillsView({ skills = [], mode, onCreate, readOnly = false }) {
|
|
4
|
+
const live = mode === 'api';
|
|
5
|
+
return <div className="main-scroll scroll fade-in">
|
|
6
|
+
<PageHead title="Skills" sub="Upload reusable custom skill bundles, then attach them to an agent version."
|
|
7
|
+
action="Upload skill" onAction={onCreate} readOnly={readOnly} endpoint="POST /v1/skills" />
|
|
8
|
+
<div className="inline-warn" role="status"><Icon name="alert" size={14} /><span>OMA supports custom skill bundles. Anthropic-hosted prebuilt skills are not provided by this appliance.</span></div>
|
|
9
|
+
{!live ? <div className="field-hint" style={{ marginTop:14 }}>Demo mode does not persist uploaded skill bundles.</div>
|
|
10
|
+
: skills.length === 0 ? <EmptyState icon="fileText" title="No custom skills" message="Upload a bundle containing SKILL.md and its supporting files, then select it when creating an agent."
|
|
11
|
+
actionLabel={!readOnly ? 'Upload skill' : null} onAction={onCreate} />
|
|
12
|
+
: <div className="panel" style={{ marginTop:14 }}>
|
|
13
|
+
<div className="thead"><span className="th grow">Skill</span><span className="th" style={{ width:130 }}>Latest version</span><span className="th" style={{ width:110 }}>Created</span></div>
|
|
14
|
+
{skills.map((skill) => <div className="trow" key={skill.id} style={{ cursor:'default' }}>
|
|
15
|
+
<span className="td grow"><span className="cell-strong">{skill.display_title}</span><div className="mono" style={{ fontSize:11.5, color:'var(--faint)', marginTop:3 }}>{skill.id}</div></span>
|
|
16
|
+
<span className="td mono" style={{ width:130, color:'var(--soft)' }}>{skill.latest_version || '—'}</span>
|
|
17
|
+
<span className="td mono" style={{ width:110, color:'var(--faint)' }}>{skill.created_at ? new Date(skill.created_at).toLocaleDateString() : '—'}</span>
|
|
18
|
+
</div>)}
|
|
19
|
+
</div>}
|
|
20
|
+
</div>;
|
|
21
|
+
}
|