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,200 @@
|
|
|
1
|
+
// docs.jsx — Markdown-backed, public in-console documentation. → window
|
|
2
|
+
// Content lives in docs/*.md so the reader, copy action, and raw source share
|
|
3
|
+
// one OMA-authored page. The small renderer intentionally supports only the
|
|
4
|
+
// safe Markdown subset used by those local pages; it never renders raw HTML.
|
|
5
|
+
const { useEffect: useEffectDocs, useMemo: useMemoDocs, useState: useStateDocs } = React;
|
|
6
|
+
|
|
7
|
+
const DOC_NAV = [
|
|
8
|
+
{ label:'First steps', pages:[['overview', 'Overview'], ['quickstart', 'Quickstart'], ['console', 'Prototype in Console'], ['migration', 'Migration']] },
|
|
9
|
+
{ label:'Define your agent', pages:[['agents', 'Agent setup'], ['tools', 'Tools'], ['permissions', 'Permission policies'], ['skills', 'Skills'], ['integrations', 'MCP connector']] },
|
|
10
|
+
{ label:'Configure agent environment', pages:[['environments', 'Environments'], ['sandbox-reference', 'Cloud sandbox reference'], ['self-hosted-sandboxes', 'Self-hosted sandboxes'], ['sandbox-security', 'Sandbox security']] },
|
|
11
|
+
{ label:'Delegate work to your agent', pages:[['sessions', 'Start a session'], ['session-operations', 'Session operations'], ['events', 'Session event stream'], ['files', 'Files'], ['vaults', 'Vaults']] },
|
|
12
|
+
{ label:'Beyond v1', pages:[['github', 'GitHub'], ['outcomes', 'Define outcomes'], ['memory', 'Memory'], ['dreams', 'Dreams'], ['multiagent', 'Multi-agent orchestration'], ['scheduled-deployments', 'Scheduled deployments'], ['webhooks', 'Webhooks']] },
|
|
13
|
+
{ label:'Reference', pages:[['reference', 'API reference and compatibility']] },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const DOC_PAGES = Object.fromEntries(DOC_NAV.flatMap((section) => section.pages.map(([id, label]) => [id, {
|
|
17
|
+
label,
|
|
18
|
+
crumb:section.label,
|
|
19
|
+
path:`docs/${id}.md`,
|
|
20
|
+
}])));
|
|
21
|
+
|
|
22
|
+
function slug(value) {
|
|
23
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function openDocumentationTarget(target) {
|
|
27
|
+
if (target.startsWith('/') || /^https:\/\//.test(target)) window.open(target, target.startsWith('https://') ? '_blank' : '_self', 'noopener,noreferrer');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function InlineMarkdown({ text, onNavigate }) {
|
|
31
|
+
const parts = text.split(/(`[^`]+`|\[[^\]]+\]\([^\s)]+\)|\*\*[^*]+\*\*)/g);
|
|
32
|
+
return <>{parts.map((part, index) => {
|
|
33
|
+
if (part.startsWith('`') && part.endsWith('`')) return <code key={index}>{part.slice(1, -1)}</code>;
|
|
34
|
+
if (part.startsWith('**') && part.endsWith('**')) return <strong key={index}>{part.slice(2, -2)}</strong>;
|
|
35
|
+
const link = /^\[([^\]]+)\]\(([^\s)]+)\)$/.exec(part);
|
|
36
|
+
if (link) {
|
|
37
|
+
const [, label, target] = link;
|
|
38
|
+
if (target.startsWith('#docs=')) return <button type="button" key={index} className="docs-inline-link" onClick={() => onNavigate(target.slice(6))}>{label}</button>;
|
|
39
|
+
if (target.startsWith('/') || /^https:\/\//.test(target)) return <button type="button" key={index} className="docs-inline-link" onClick={() => openDocumentationTarget(target)}>{label}</button>;
|
|
40
|
+
return <React.Fragment key={index}>{label}</React.Fragment>;
|
|
41
|
+
}
|
|
42
|
+
return <React.Fragment key={index}>{part}</React.Fragment>;
|
|
43
|
+
})}</>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function MarkdownDocument({ markdown, onNavigate, skipTitle = false }) {
|
|
47
|
+
const rendered = useMemoDocs(() => {
|
|
48
|
+
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
49
|
+
const nodes = [];
|
|
50
|
+
const headings = [];
|
|
51
|
+
let index = 0;
|
|
52
|
+
|
|
53
|
+
const paragraph = () => {
|
|
54
|
+
const words = [];
|
|
55
|
+
while (index < lines.length && lines[index].trim() && !/^(#{1,3}\s|```|> \[!|[-*] |\d+\. |\|)/.test(lines[index])) {
|
|
56
|
+
words.push(lines[index].trim());
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
59
|
+
if (words.length) nodes.push(<p key={`p-${index}`}><InlineMarkdown text={words.join(' ')} onNavigate={onNavigate} /></p>);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
while (index < lines.length) {
|
|
63
|
+
const line = lines[index];
|
|
64
|
+
if (!line.trim()) { index += 1; continue; }
|
|
65
|
+
const heading = /^(#{1,3})\s+(.+)$/.exec(line);
|
|
66
|
+
if (heading) {
|
|
67
|
+
const [, marks, text] = heading;
|
|
68
|
+
const id = slug(text);
|
|
69
|
+
if (marks.length === 1) {
|
|
70
|
+
if (!skipTitle) nodes.push(<h1 key={`h1-${id}`}>{text}</h1>);
|
|
71
|
+
}
|
|
72
|
+
else if (marks.length === 2) {
|
|
73
|
+
headings.push([id, text]);
|
|
74
|
+
nodes.push(<h2 id={id} key={`h2-${id}`}>{text}</h2>);
|
|
75
|
+
} else {
|
|
76
|
+
headings.push([id, text]);
|
|
77
|
+
nodes.push(<h3 id={id} key={`h3-${id}`}>{text}</h3>);
|
|
78
|
+
}
|
|
79
|
+
index += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const callout = /^> \[!(NOTE|WARNING)\]\s*(.*)$/.exec(line);
|
|
83
|
+
if (callout) {
|
|
84
|
+
const [, tone, text] = callout;
|
|
85
|
+
nodes.push(<aside key={`callout-${index}`} className={'docs-callout ' + (tone === 'WARNING' ? 'warn' : 'info')}><Icon name={tone === 'WARNING' ? 'alert' : 'info'} size={16} /><div><InlineMarkdown text={text} onNavigate={onNavigate} /></div></aside>);
|
|
86
|
+
index += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (line.startsWith('```')) {
|
|
90
|
+
const body = [];
|
|
91
|
+
index += 1;
|
|
92
|
+
while (index < lines.length && !lines[index].startsWith('```')) { body.push(lines[index]); index += 1; }
|
|
93
|
+
if (index < lines.length) index += 1;
|
|
94
|
+
nodes.push(<pre className="docs-code" key={`code-${index}`}><code>{body.join('\n')}</code></pre>);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (/^[-*] /.test(line) || /^\d+\. /.test(line)) {
|
|
98
|
+
const ordered = /^\d+\. /.test(line);
|
|
99
|
+
const items = [];
|
|
100
|
+
while (index < lines.length && (ordered ? /^\d+\. /.test(lines[index]) : /^[-*] /.test(lines[index]))) {
|
|
101
|
+
items.push(lines[index].replace(ordered ? /^\d+\. / : /^[-*] /, ''));
|
|
102
|
+
index += 1;
|
|
103
|
+
}
|
|
104
|
+
const List = ordered ? 'ol' : 'ul';
|
|
105
|
+
nodes.push(<List className={ordered ? 'docs-steps compact' : 'docs-list'} key={`list-${index}`}>{items.map((item, itemIndex) => <li key={itemIndex}><InlineMarkdown text={item} onNavigate={onNavigate} /></li>)}</List>);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (line.startsWith('|') && index + 1 < lines.length && /^\|?\s*:?-{3,}/.test(lines[index + 1])) {
|
|
109
|
+
const header = line.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
110
|
+
index += 2;
|
|
111
|
+
const rows = [];
|
|
112
|
+
while (index < lines.length && lines[index].startsWith('|')) { rows.push(lines[index].split('|').slice(1, -1).map((cell) => cell.trim())); index += 1; }
|
|
113
|
+
nodes.push(<div className="docs-table-wrap" key={`table-${index}`}><table className="docs-table"><thead><tr>{header.map((cell) => <th key={cell}>{cell}</th>)}</tr></thead><tbody>{rows.map((row, rowIndex) => <tr key={rowIndex}>{row.map((cell, cellIndex) => <td key={cellIndex}><InlineMarkdown text={cell} onNavigate={onNavigate} /></td>)}</tr>)}</tbody></table></div>);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
paragraph();
|
|
117
|
+
}
|
|
118
|
+
return { nodes, headings };
|
|
119
|
+
}, [markdown, onNavigate]);
|
|
120
|
+
return <>{rendered.nodes}</>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function markdownHeadings(markdown) {
|
|
124
|
+
return markdown.match(/^#{2,3}\s+.+$/gm)?.map((line) => {
|
|
125
|
+
const text = line.replace(/^#{2,3}\s+/, '');
|
|
126
|
+
return [slug(text), text, line.startsWith('###')];
|
|
127
|
+
}) || [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function markdownTitle(markdown, fallback) {
|
|
131
|
+
return /^#\s+(.+)$/m.exec(markdown)?.[1] || fallback;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function DocsView({ page = 'overview', onNavigate, goConsole }) {
|
|
135
|
+
const activePage = DOC_PAGES[page] ? page : 'overview';
|
|
136
|
+
const current = DOC_PAGES[activePage];
|
|
137
|
+
const [markdown, setMarkdown] = useStateDocs('');
|
|
138
|
+
const [state, setState] = useStateDocs('loading');
|
|
139
|
+
const [copied, setCopied] = useStateDocs(false);
|
|
140
|
+
const [menuOpen, setMenuOpen] = useStateDocs(false);
|
|
141
|
+
const [query, setQuery] = useStateDocs('');
|
|
142
|
+
const headings = markdownHeadings(markdown);
|
|
143
|
+
const title = markdownTitle(markdown, current.label);
|
|
144
|
+
|
|
145
|
+
useEffectDocs(() => {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
setState('loading');
|
|
148
|
+
setCopied(false);
|
|
149
|
+
setMenuOpen(false);
|
|
150
|
+
fetch(current.path, { signal:controller.signal })
|
|
151
|
+
.then((response) => response.ok ? response.text() : Promise.reject(new Error(`Documentation unavailable (${response.status})`)))
|
|
152
|
+
.then((text) => { setMarkdown(text); setState('loaded'); })
|
|
153
|
+
.catch((error) => { if (error.name !== 'AbortError') { setMarkdown(''); setState('error'); } });
|
|
154
|
+
return () => controller.abort();
|
|
155
|
+
}, [current.path]);
|
|
156
|
+
|
|
157
|
+
const copyMarkdown = async () => {
|
|
158
|
+
try {
|
|
159
|
+
await navigator.clipboard.writeText(markdown);
|
|
160
|
+
setCopied(true);
|
|
161
|
+
} catch {
|
|
162
|
+
setCopied(false);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const openMarkdown = () => window.open(new URL(current.path, window.location.href).toString(), '_blank', 'noopener,noreferrer');
|
|
166
|
+
const go = (next) => onNavigate(DOC_PAGES[next] ? next : 'overview');
|
|
167
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
168
|
+
const visibleNav = DOC_NAV.map((section) => ({
|
|
169
|
+
...section,
|
|
170
|
+
pages: normalizedQuery
|
|
171
|
+
? section.pages.filter(([, label]) => label.toLowerCase().includes(normalizedQuery))
|
|
172
|
+
: section.pages,
|
|
173
|
+
})).filter((section) => section.pages.length > 0);
|
|
174
|
+
|
|
175
|
+
return <div className="docs-shell">
|
|
176
|
+
<header className="docs-topbar">
|
|
177
|
+
<button type="button" className="docs-topbar-brand" onClick={() => goConsole('start')}><span className="brand-mark">O</span><span>Open Managed Agents</span></button>
|
|
178
|
+
<nav className="docs-topbar-nav" aria-label="Documentation sections"><span>Managed Agents</span><span className="active">Documentation</span></nav>
|
|
179
|
+
<div className="docs-topbar-actions"><a href="/docs/" className="docs-topbar-link"><Icon name="terminal" size={15} />API reference</a><button type="button" className="docs-topbar-console" onClick={() => goConsole('start')}><Icon name="arrowRight" size={15} />Console</button></div>
|
|
180
|
+
</header>
|
|
181
|
+
<nav className="docs-nav scroll" aria-label="Documentation navigation">
|
|
182
|
+
<label className="docs-search"><Icon name="search" size={16} /><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search documentation" aria-label="Search documentation" /><kbd>⌘K</kbd></label>
|
|
183
|
+
{visibleNav.map((section) => <div className="docs-nav-group" key={section.label}><div className="docs-nav-label">{section.label}</div>{section.pages.map(([id, label]) => <button key={id} className={'docs-nav-link' + (id === activePage ? ' active' : '')} onClick={() => go(id)}>{label}</button>)}</div>)}
|
|
184
|
+
{visibleNav.length === 0 && <div className="docs-nav-empty">No pages match “{query}”.</div>}
|
|
185
|
+
<div className="docs-nav-footer"><span>OMA documentation</span><small>Local alpha guide</small></div>
|
|
186
|
+
</nav>
|
|
187
|
+
<main className="docs-main scroll">
|
|
188
|
+
<article className="docs-article">
|
|
189
|
+
<div className="docs-crumbs">Managed Agents <span>/</span> {current.crumb}</div>
|
|
190
|
+
<div className="docs-title-row"><h1>{title}</h1><div className="docs-page-actions"><button type="button" className="docs-copy-main" onClick={copyMarkdown} disabled={state !== 'loaded'}><Icon name="copy" size={15} />{copied ? 'Copied!' : 'Copy page'}</button><button type="button" className="docs-copy-caret" aria-label="Documentation actions" aria-expanded={menuOpen} onClick={() => setMenuOpen((open) => !open)}>⌄</button>{menuOpen && <div className="docs-copy-menu"><button type="button" onClick={() => { copyMarkdown(); setMenuOpen(false); }}><Icon name="copy" size={14} />Copy page as Markdown</button><button type="button" onClick={() => { openMarkdown(); setMenuOpen(false); }}><Icon name="arrowRight" size={14} />Open Markdown</button></div>}</div></div>
|
|
191
|
+
{state === 'loading' && <div className="docs-loading" aria-busy="true">Loading documentation…</div>}
|
|
192
|
+
{state === 'error' && <div className="docs-loading error"><Icon name="alert" size={16} />The local Markdown source could not be loaded.</div>}
|
|
193
|
+
{state === 'loaded' && <MarkdownDocument markdown={markdown} onNavigate={go} skipTitle />}
|
|
194
|
+
</article>
|
|
195
|
+
</main>
|
|
196
|
+
<aside className="docs-toc" aria-label="On this page"><strong>On this page</strong>{headings.map(([id, label, nested]) => <button key={id} className={nested ? 'nested' : ''} onClick={() => document.getElementById(id)?.scrollIntoView({ behavior:'smooth', block:'start' })}>{label}</button>)}</aside>
|
|
197
|
+
</div>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
window.DocsView = DocsView;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// environments.jsx — readiness and environment list/create surfaces → window
|
|
2
|
+
const { useState: useStateE } = React;
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ENV_CONFIG = {
|
|
5
|
+
networking: { type: "limited", allowed_hosts: [] },
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function presetCopy(preset) {
|
|
9
|
+
return {
|
|
10
|
+
label: preset.label || preset.id,
|
|
11
|
+
description: preset.description || "Deployment-defined limited egress preset.",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function networkingConfigForHosts(hosts) {
|
|
16
|
+
return { networking: { type: "limited", allowed_hosts: hosts } };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function splitCustomHosts(text) {
|
|
20
|
+
return text
|
|
21
|
+
.split(/[\n,]+/)
|
|
22
|
+
.map((item) => item.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function previewHosts(hosts) {
|
|
27
|
+
if (!hosts.length) return "No hosts; all outbound network access remains blocked.";
|
|
28
|
+
return hosts.join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function EnvironmentError({ error }) {
|
|
32
|
+
if (!error) return null;
|
|
33
|
+
return (
|
|
34
|
+
<div className="banner" role="alert">
|
|
35
|
+
<Icon name="alert" size={16} />
|
|
36
|
+
<div>
|
|
37
|
+
<div className="b-main">Environment request failed</div>
|
|
38
|
+
<div className="b-sub">{error.message}</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CreateEnvironmentModal({ mode, networkingCatalog, onClose, onCreated, onAuthExpired, api = window.OmaConsoleApi }) {
|
|
45
|
+
const [name, setName] = useStateE("");
|
|
46
|
+
const catalog = networkingCatalog?.presets?.length
|
|
47
|
+
? networkingCatalog.presets
|
|
48
|
+
: [{ id:"offline-v1", label:"Offline", description:"No external network access.", allowed_hosts:[], config:DEFAULT_ENV_CONFIG }];
|
|
49
|
+
const deployment = networkingCatalog?.deployment ?? {
|
|
50
|
+
provider:null,
|
|
51
|
+
egress_supported:false,
|
|
52
|
+
reason:"Networking capability was not reported by this deployment.",
|
|
53
|
+
};
|
|
54
|
+
const [presetId, setPresetId] = useStateE(catalog[0].id);
|
|
55
|
+
const [customHostsText, setCustomHostsText] = useStateE("");
|
|
56
|
+
const [validatedHosts, setValidatedHosts] = useStateE(null);
|
|
57
|
+
const [validationBusy, setValidationBusy] = useStateE(false);
|
|
58
|
+
const [busy, setBusy] = useStateE(false);
|
|
59
|
+
const [error, setError] = useStateE(null);
|
|
60
|
+
const trimmed = name.trim();
|
|
61
|
+
const selectedPreset = catalog.find((preset) => preset.id === presetId) ?? catalog[0];
|
|
62
|
+
const selectedCopy = presetId === "custom"
|
|
63
|
+
? { label:"Custom allowlist", description:"Validated custom limited egress." }
|
|
64
|
+
: presetCopy(selectedPreset);
|
|
65
|
+
const customHosts = splitCustomHosts(customHostsText);
|
|
66
|
+
const selectedHosts = presetId === "custom"
|
|
67
|
+
? (validatedHosts ?? [])
|
|
68
|
+
: (selectedPreset?.config?.networking?.allowed_hosts ?? selectedPreset?.allowed_hosts ?? []);
|
|
69
|
+
const selectedConfig = networkingConfigForHosts(selectedHosts);
|
|
70
|
+
const selectedNeedsEgress = selectedHosts.length > 0;
|
|
71
|
+
const capabilityReady = !selectedNeedsEgress || deployment.egress_supported === true;
|
|
72
|
+
const customValid = presetId !== "custom" || (Array.isArray(validatedHosts) && validatedHosts.length > 0);
|
|
73
|
+
const valid = trimmed.length > 0 && mode === "api" && !busy && !validationBusy && customValid && capabilityReady;
|
|
74
|
+
|
|
75
|
+
const choosePreset = (id) => {
|
|
76
|
+
setPresetId(id);
|
|
77
|
+
setValidatedHosts(null);
|
|
78
|
+
setError(null);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const validateCustom = () => {
|
|
82
|
+
if (mode !== "api" || !customHosts.length || validationBusy || busy) return;
|
|
83
|
+
setValidationBusy(true);
|
|
84
|
+
setError(null);
|
|
85
|
+
api.validateEnvironmentNetworkingHosts(customHosts)
|
|
86
|
+
.then((hosts) => setValidatedHosts(hosts))
|
|
87
|
+
.catch((err) => {
|
|
88
|
+
if (err.status === 401 && onAuthExpired) onAuthExpired();
|
|
89
|
+
setValidatedHosts(null);
|
|
90
|
+
setError(err);
|
|
91
|
+
})
|
|
92
|
+
.finally(() => setValidationBusy(false));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const submit = () => {
|
|
96
|
+
if (!valid) return;
|
|
97
|
+
setBusy(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
api.createEnvironment({ name: trimmed, config: selectedConfig })
|
|
100
|
+
.then(onCreated)
|
|
101
|
+
.catch((err) => {
|
|
102
|
+
if (err.status === 401 && onAuthExpired) onAuthExpired();
|
|
103
|
+
setError(err);
|
|
104
|
+
})
|
|
105
|
+
.finally(() => setBusy(false));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Modal icon="database" title="Create environment" sub="Create an immutable sandbox environment with an explicit limited-network policy." onClose={busy ? () => {} : onClose}
|
|
110
|
+
footer={<>
|
|
111
|
+
<span className="left">Sends to <span className="mono">POST /v1/environments</span></span>
|
|
112
|
+
<button className="btn" disabled={busy} onClick={onClose}>Cancel</button>
|
|
113
|
+
<button className="btn btn-primary" disabled={!valid} onClick={submit} aria-busy={busy}>
|
|
114
|
+
<Icon name={busy ? "refresh" : "plus"} size={15} />{busy ? "Creating..." : "Create environment"}</button>
|
|
115
|
+
</>}>
|
|
116
|
+
{mode !== "api" && (
|
|
117
|
+
<div className="inline-warn">
|
|
118
|
+
<Icon name="alert" size={14} />
|
|
119
|
+
<span>Environment creation is available only against the live API.</span>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
<EnvironmentError error={error} />
|
|
123
|
+
<Labeled label="Name" htmlFor="create-environment-name" hint="Stored as the environment name. The API generates the environment ID.">
|
|
124
|
+
<input id="create-environment-name" name="name" className="input" autoFocus placeholder="e.g. default-docker-local" value={name}
|
|
125
|
+
disabled={busy} onChange={(e) => { setName(e.target.value); setError(null); }} />
|
|
126
|
+
</Labeled>
|
|
127
|
+
<Labeled label="Networking preset" hint="Policy controls allowed destinations. The server separately reports whether this deployment can enforce egress.">
|
|
128
|
+
<div className={deployment.egress_supported ? "inline-ok" : "inline-warn"} role="status">
|
|
129
|
+
<Icon name={deployment.egress_supported ? "checkCircle" : "alert"} size={14} />
|
|
130
|
+
<span>{deployment.egress_supported
|
|
131
|
+
? `${deployment.provider || "This deployment"} supports approved HTTPS egress.`
|
|
132
|
+
: (deployment.reason || "This deployment cannot run network-enabled environments.")}</span>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="env-preset-grid" role="radiogroup" aria-label="Networking preset">
|
|
135
|
+
{catalog.map((preset) => {
|
|
136
|
+
const copy = presetCopy(preset);
|
|
137
|
+
return (
|
|
138
|
+
<button type="button" className={'env-choice' + (presetId === preset.id ? ' on' : '')} key={preset.id}
|
|
139
|
+
disabled={busy || (preset.config?.networking?.allowed_hosts?.length > 0 && !deployment.egress_supported)} role="radio" aria-checked={presetId === preset.id} onClick={() => choosePreset(preset.id)}>
|
|
140
|
+
<span className="dot-r" />
|
|
141
|
+
<span>
|
|
142
|
+
<span className="r-main">{copy.label}</span>
|
|
143
|
+
<span className="r-sub">{copy.description}</span>
|
|
144
|
+
</span>
|
|
145
|
+
</button>
|
|
146
|
+
);
|
|
147
|
+
})}
|
|
148
|
+
<button type="button" className={'env-choice' + (presetId === "custom" ? ' on' : '')}
|
|
149
|
+
disabled={busy || !deployment.egress_supported} role="radio" aria-checked={presetId === "custom"} onClick={() => choosePreset("custom")}>
|
|
150
|
+
<span className="dot-r" />
|
|
151
|
+
<span>
|
|
152
|
+
<span className="r-main">Custom allowlist</span>
|
|
153
|
+
<span className="r-sub">Enter exact hosts or supported leading wildcards.</span>
|
|
154
|
+
</span>
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</Labeled>
|
|
158
|
+
{presetId === "custom" && (
|
|
159
|
+
<Labeled label="Allowed hosts" htmlFor="create-environment-hosts" hint="Use commas or new lines. A wildcard such as *.example.com matches subdomains only; add example.com separately for the bare domain.">
|
|
160
|
+
<textarea id="create-environment-hosts" className="textarea" value={customHostsText} disabled={busy}
|
|
161
|
+
placeholder={"registry.npmjs.org\n*.pypi.org\nfiles.pythonhosted.org"}
|
|
162
|
+
onChange={(event) => { setCustomHostsText(event.target.value); setValidatedHosts(null); setError(null); }} />
|
|
163
|
+
<div className="env-actions">
|
|
164
|
+
<button className="btn btn-sm" disabled={!customHosts.length || validationBusy || busy || mode !== "api"} onClick={validateCustom}>
|
|
165
|
+
<Icon name={validationBusy ? "refresh" : "checkCircle"} size={14} />{validationBusy ? "Validating..." : "Validate hosts"}
|
|
166
|
+
</button>
|
|
167
|
+
<span className="field-hint">{validatedHosts ? `${validatedHosts.length} normalized host(s) accepted by server validation.` : "Create remains disabled until validation succeeds."}</span>
|
|
168
|
+
</div>
|
|
169
|
+
</Labeled>
|
|
170
|
+
)}
|
|
171
|
+
<Labeled label="Exact generated hosts" hint="Environment policies are immutable. To change networking, create a new environment and start a new session. No secrets are stored in the environment or shown to the guest.">
|
|
172
|
+
<div className="env-preset" aria-label="Generated networking policy preview">
|
|
173
|
+
<div className="tool-ico"><Icon name="database" size={18} /></div>
|
|
174
|
+
<div className="grow">
|
|
175
|
+
<div className="cell-strong">{presetId === "custom" ? "Custom limited egress" : selectedCopy.label}</div>
|
|
176
|
+
<pre className="mono env-host-preview">{previewHosts(selectedHosts)}</pre>
|
|
177
|
+
<div className="mono env-config">{JSON.stringify({ config: selectedConfig })}</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</Labeled>
|
|
181
|
+
</Modal>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function ReadinessView({ agents = [], environments = [], models = [], mode = "api", workspaceLoaded = false, go, onCreateEnvironment, onCreateAgent, onCreateSession }) {
|
|
186
|
+
const authReady = mode === "demo" || workspaceLoaded;
|
|
187
|
+
const activeAgents = agents.filter((agent) => agent.status !== "archived");
|
|
188
|
+
const activeEnvironments = environments.filter((environment) => !environment.archived);
|
|
189
|
+
const envReady = activeEnvironments.length > 0;
|
|
190
|
+
const modelReady = models.length > 0;
|
|
191
|
+
const credentialReady = models.some((model) => model.credentials_configured);
|
|
192
|
+
const readyModelKeys = new Set(models.filter((model) => model.credentials_configured).map((model) => `${model.provider}/${model.id}`));
|
|
193
|
+
const agentModelKey = (agent) => agent.modelProvider && agent.modelId
|
|
194
|
+
? `${agent.modelProvider}/${agent.modelId}`
|
|
195
|
+
: String(agent.model || "").includes("/") ? String(agent.model) : `anthropic/${agent.model}`;
|
|
196
|
+
const agentReady = activeAgents.some((agent) => readyModelKeys.has(agentModelKey(agent)));
|
|
197
|
+
const ready = authReady && agentReady && envReady && modelReady && credentialReady;
|
|
198
|
+
const items = [
|
|
199
|
+
{
|
|
200
|
+
key: "auth",
|
|
201
|
+
title: "Workspace access",
|
|
202
|
+
ok: authReady,
|
|
203
|
+
detail: authReady ? "The console can read workspace-scoped /v1 resources." : "Enter a workspace key or browse from Admin.",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
key: "model",
|
|
207
|
+
title: "Model and credentials",
|
|
208
|
+
ok: modelReady && credentialReady,
|
|
209
|
+
detail: !modelReady
|
|
210
|
+
? "No selectable models were returned by this deployment."
|
|
211
|
+
: credentialReady
|
|
212
|
+
? `${models.filter((model) => model.credentials_configured).length} selectable model(s) report configured credentials.`
|
|
213
|
+
: "Models are registered, but none report configured credentials. Run oma doctor, then oma auth set <provider>.",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
key: "agent",
|
|
217
|
+
title: "Runnable agent present",
|
|
218
|
+
ok: agentReady,
|
|
219
|
+
detail: agentReady
|
|
220
|
+
? `${activeAgents.filter((agent) => readyModelKeys.has(agentModelKey(agent))).length} active agent(s) use a credential-ready model.`
|
|
221
|
+
: activeAgents.length > 0
|
|
222
|
+
? "Active agents exist, but none use a credential-ready model. Create an agent with a ready model."
|
|
223
|
+
: "Create an agent before starting a session.",
|
|
224
|
+
action: agentReady ? () => go("agents") : onCreateAgent,
|
|
225
|
+
actionLabel: agentReady ? "View agents" : "Create agent",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
key: "environment",
|
|
229
|
+
title: "Environment present",
|
|
230
|
+
ok: envReady,
|
|
231
|
+
detail: envReady ? `${activeEnvironments.length} environment(s) available for sessions.` : "Create a default-deny environment before starting a session.",
|
|
232
|
+
action: envReady ? () => go("environments") : onCreateEnvironment,
|
|
233
|
+
actionLabel: envReady ? "View environments" : "Create environment",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
key: "sandbox",
|
|
237
|
+
title: "Sandbox verification",
|
|
238
|
+
ok: null,
|
|
239
|
+
detail: "Sandbox execution is verified by the first session tool run, not guessed by the browser. Run oma doctor for local prerequisite checks.",
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
return (
|
|
243
|
+
<div className="main-scroll scroll fade-in">
|
|
244
|
+
<PageHead title="Start" sub="Observable readiness for the alpha console workflow." />
|
|
245
|
+
<div className="readiness-summary panel">
|
|
246
|
+
<div>
|
|
247
|
+
<h2>{ready ? "Ready for session creation" : "Workspace setup is incomplete"}</h2>
|
|
248
|
+
<p>Readiness uses the live workspace model catalog and credential flags. Sandbox execution remains unverified until a session runs a tool.</p>
|
|
249
|
+
</div>
|
|
250
|
+
<St k={ready ? "active" : "idle"} />
|
|
251
|
+
</div>
|
|
252
|
+
<div className="readiness-grid">
|
|
253
|
+
{items.map((item) => (
|
|
254
|
+
<div className="panel readiness-card" key={item.key}>
|
|
255
|
+
<div className="readiness-card-head">
|
|
256
|
+
<span className={'readiness-mark ' + (item.ok ? 'ok' : 'todo')}>
|
|
257
|
+
<Icon name={item.ok === null ? "info" : item.ok ? "checkCircle" : "alert"} size={16} />
|
|
258
|
+
</span>
|
|
259
|
+
<div>
|
|
260
|
+
<h3>{item.title}</h3>
|
|
261
|
+
<p>{item.detail}</p>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
{item.action && <button className="btn btn-sm" onClick={item.action}>{item.actionLabel}</button>}
|
|
265
|
+
</div>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
{ready && <button className="btn btn-primary" onClick={onCreateSession}><Icon name="plus" size={15} />Create session</button>}
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function EnvironmentsView({ environments = [], mode = "api", dataState = "loaded", onCreate, createdEnvironmentId, onArchive, onDelete }) {
|
|
274
|
+
const [confirm, setConfirm] = useStateE(null);
|
|
275
|
+
const [actionBusy, setActionBusy] = useStateE(false);
|
|
276
|
+
const [actionError, setActionError] = useStateE(null);
|
|
277
|
+
const loading = dataState === "loading";
|
|
278
|
+
const error = dataState === "error";
|
|
279
|
+
const partial = dataState === "partial";
|
|
280
|
+
const empty = dataState === "empty" || environments.length === 0;
|
|
281
|
+
const readOnly = mode !== "api";
|
|
282
|
+
const runLifecycle = async () => {
|
|
283
|
+
if (!confirm || actionBusy) return;
|
|
284
|
+
setActionBusy(true);
|
|
285
|
+
setActionError(null);
|
|
286
|
+
try {
|
|
287
|
+
if (confirm.kind === 'archive') await onArchive(confirm.environment);
|
|
288
|
+
else await onDelete(confirm.environment);
|
|
289
|
+
setConfirm(null);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
setActionError(error);
|
|
292
|
+
} finally {
|
|
293
|
+
setActionBusy(false);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
return (
|
|
297
|
+
<div className="main-scroll scroll fade-in">
|
|
298
|
+
<PageHead title="Environments" sub="Sandbox execution environment configuration."
|
|
299
|
+
action="Create environment" onAction={onCreate} readOnly={readOnly}
|
|
300
|
+
endpoint="POST /v1/environments" />
|
|
301
|
+
<div className="toolbar">
|
|
302
|
+
<Field wide placeholder="Search by environment ID" />
|
|
303
|
+
<Select label="Networking" value="All" w={150} />
|
|
304
|
+
</div>
|
|
305
|
+
<div className="inline-note" role="status">
|
|
306
|
+
<Icon name="info" size={14} />
|
|
307
|
+
<span>Environment configurations are immutable. Archive hides an environment from new sessions while preserving it for existing sessions; deletion is available only when no session references it.</span>
|
|
308
|
+
</div>
|
|
309
|
+
{partial && <PartialNotice resource="environments" />}
|
|
310
|
+
{createdEnvironmentId && (
|
|
311
|
+
<div className="inline-ok" role="status">
|
|
312
|
+
<Icon name="checkCircle" size={14} />
|
|
313
|
+
<span>Created <span className="mono">{createdEnvironmentId}</span>. Refresh before retrying if a later request is ambiguous.</span>
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
316
|
+
{loading ? <SkeletonTable rows={4} cols={[150, 'grow', 210, 90]} lead={false} />
|
|
317
|
+
: error ? <ErrorState resource="environments" onRetry={() => {}} />
|
|
318
|
+
: empty ? <EmptyState icon="database" title="No environments yet"
|
|
319
|
+
message="Create a default-deny alpha environment before starting a session."
|
|
320
|
+
actionLabel={!readOnly ? "Create environment" : null} onAction={onCreate} />
|
|
321
|
+
: <>
|
|
322
|
+
<div className="panel">
|
|
323
|
+
<div className="thead">
|
|
324
|
+
<span className="th" style={{ width:150 }}>ID</span>
|
|
325
|
+
<span className="th grow">Name</span>
|
|
326
|
+
<span className="th" style={{ width:230 }}>Preset</span>
|
|
327
|
+
<span className="th" style={{ width:90 }}>Status</span>
|
|
328
|
+
<span className="th" style={{ width:70 }}>Created</span>
|
|
329
|
+
<span className="th" style={{ width:170 }}>Actions</span>
|
|
330
|
+
</div>
|
|
331
|
+
{environments.map((environment) => (
|
|
332
|
+
<div className="trow env-row" key={environment.id}>
|
|
333
|
+
<span className="td mono" style={{ width:150, fontSize:12, color:'var(--soft)' }}>{environment.id}</span>
|
|
334
|
+
<span className="td grow cell-strong">{environment.label}</span>
|
|
335
|
+
<span className="td mono" style={{ width:230, fontSize:12, color:'var(--soft)' }}>{environment.networkingSummary || environment.image}</span>
|
|
336
|
+
<span className="td" style={{ width:90 }}><St k={environment.archived ? "archived" : "active"} /></span>
|
|
337
|
+
<span className="td mono" style={{ width:70, color:'var(--faint)' }}>{environment.created || "—"}</span>
|
|
338
|
+
<span className="td" style={{ width:170, display:'flex', gap:6, justifyContent:'flex-end' }}>
|
|
339
|
+
{!environment.archived && <button className="btn btn-sm" disabled={readOnly} title={readOnly ? 'Connect a live workspace to archive this environment.' : undefined} onClick={() => { setActionError(null); setConfirm({ kind:'archive', environment }); }}><Icon name="archive" size={13} />Archive</button>}
|
|
340
|
+
<button className="btn btn-sm btn-danger" disabled={readOnly} title={readOnly ? 'Connect a live workspace to delete this environment.' : undefined} onClick={() => { setActionError(null); setConfirm({ kind:'delete', environment }); }}><Icon name="trash" size={13} />Delete</button>
|
|
341
|
+
</span>
|
|
342
|
+
</div>
|
|
343
|
+
))}
|
|
344
|
+
</div>
|
|
345
|
+
<Pager />
|
|
346
|
+
</>}
|
|
347
|
+
{confirm && <ConfirmDialog
|
|
348
|
+
icon={confirm.kind === 'archive' ? 'archive' : 'trash'}
|
|
349
|
+
danger={confirm.kind === 'delete'}
|
|
350
|
+
title={confirm.kind === 'archive' ? 'Archive environment' : 'Delete environment'}
|
|
351
|
+
message={confirm.kind === 'archive'
|
|
352
|
+
? <>Archive <b>{confirm.environment.label}</b>? It will no longer be available for new sessions. Existing sessions retain their environment.</>
|
|
353
|
+
: <>Delete <b>{confirm.environment.label}</b>? This is permanent. OMA refuses deletion when any session still references it.</>}
|
|
354
|
+
confirmLabel={confirm.kind === 'archive' ? 'Archive environment' : 'Delete environment'}
|
|
355
|
+
endpoint={confirm.kind === 'archive' ? 'POST /v1/environments/:id/archive' : 'DELETE /v1/environments/:id'}
|
|
356
|
+
onClose={() => !actionBusy && setConfirm(null)} onConfirm={runLifecycle} busy={actionBusy} error={actionError} />}
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Object.assign(window, {
|
|
362
|
+
CreateEnvironmentModal,
|
|
363
|
+
EnvironmentsView,
|
|
364
|
+
ReadinessView,
|
|
365
|
+
});
|