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.
Files changed (197) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +248 -0
  3. package/bin/oma.mjs +499 -0
  4. package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
  5. package/dist/scripts/alpha-smoke-models.mjs +92 -0
  6. package/dist/scripts/alpha-smoke.mjs +581 -0
  7. package/dist/scripts/oma-doctor.js +230 -0
  8. package/dist/scripts/oma-models.js +299 -0
  9. package/dist/scripts/oma-workspaces.js +133 -0
  10. package/dist/src/control-plane/admin/auth.js +43 -0
  11. package/dist/src/control-plane/admin/routes.js +76 -0
  12. package/dist/src/control-plane/admin/service.js +102 -0
  13. package/dist/src/control-plane/admission.js +90 -0
  14. package/dist/src/control-plane/agents/routes.js +63 -0
  15. package/dist/src/control-plane/agents/service.js +687 -0
  16. package/dist/src/control-plane/agents/store.js +391 -0
  17. package/dist/src/control-plane/agents/types.js +1 -0
  18. package/dist/src/control-plane/api-constants.js +3 -0
  19. package/dist/src/control-plane/app.js +907 -0
  20. package/dist/src/control-plane/console/auth.js +68 -0
  21. package/dist/src/control-plane/console/static.js +112 -0
  22. package/dist/src/control-plane/deployment-runtime-config.js +328 -0
  23. package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
  24. package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
  25. package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
  26. package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
  27. package/dist/src/control-plane/deployment-storage.js +354 -0
  28. package/dist/src/control-plane/egress/image.js +48 -0
  29. package/dist/src/control-plane/egress/policy.js +571 -0
  30. package/dist/src/control-plane/egress/presets.js +77 -0
  31. package/dist/src/control-plane/egress/proxy.js +72 -0
  32. package/dist/src/control-plane/egress/ssrf.js +106 -0
  33. package/dist/src/control-plane/egress/utils/debug.js +14 -0
  34. package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
  35. package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
  36. package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
  37. package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
  38. package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
  39. package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
  40. package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
  41. package/dist/src/control-plane/environments/routes.js +54 -0
  42. package/dist/src/control-plane/environments/service.js +112 -0
  43. package/dist/src/control-plane/environments/store.js +132 -0
  44. package/dist/src/control-plane/environments/types.js +1 -0
  45. package/dist/src/control-plane/errors.js +81 -0
  46. package/dist/src/control-plane/events/broadcaster.js +217 -0
  47. package/dist/src/control-plane/events/constants.js +2 -0
  48. package/dist/src/control-plane/events/persist.js +39 -0
  49. package/dist/src/control-plane/events/request.js +190 -0
  50. package/dist/src/control-plane/events/routes.js +159 -0
  51. package/dist/src/control-plane/events/runtime-helpers.js +134 -0
  52. package/dist/src/control-plane/events/service.js +1985 -0
  53. package/dist/src/control-plane/events/session-guards.js +29 -0
  54. package/dist/src/control-plane/events/sse.js +6 -0
  55. package/dist/src/control-plane/events/store.js +772 -0
  56. package/dist/src/control-plane/events/tool-persistence.js +102 -0
  57. package/dist/src/control-plane/events/types.js +21 -0
  58. package/dist/src/control-plane/files/routes.js +121 -0
  59. package/dist/src/control-plane/files/service.js +65 -0
  60. package/dist/src/control-plane/files/store-common.js +87 -0
  61. package/dist/src/control-plane/files/store-local.js +488 -0
  62. package/dist/src/control-plane/files/store-memory.js +270 -0
  63. package/dist/src/control-plane/files/store.js +2 -0
  64. package/dist/src/control-plane/files/types.js +7 -0
  65. package/dist/src/control-plane/http.js +25 -0
  66. package/dist/src/control-plane/ids.js +43 -0
  67. package/dist/src/control-plane/logging.js +263 -0
  68. package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
  69. package/dist/src/control-plane/models/catalog.js +78 -0
  70. package/dist/src/control-plane/models/config-security.js +376 -0
  71. package/dist/src/control-plane/models/deployment-config.js +93 -0
  72. package/dist/src/control-plane/models/routes.js +32 -0
  73. package/dist/src/control-plane/models/service.js +138 -0
  74. package/dist/src/control-plane/observability/instruments.js +65 -0
  75. package/dist/src/control-plane/observability/metrics.js +125 -0
  76. package/dist/src/control-plane/observability/routes.js +95 -0
  77. package/dist/src/control-plane/openapi/document.js +282 -0
  78. package/dist/src/control-plane/openapi/routes.js +78 -0
  79. package/dist/src/control-plane/request-idempotency.js +85 -0
  80. package/dist/src/control-plane/secrets/envelope.js +132 -0
  81. package/dist/src/control-plane/secrets/master-key.js +44 -0
  82. package/dist/src/control-plane/secrets/routes.js +20 -0
  83. package/dist/src/control-plane/secrets/service.js +71 -0
  84. package/dist/src/control-plane/secrets/store.js +212 -0
  85. package/dist/src/control-plane/secrets/types.js +3 -0
  86. package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
  87. package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
  88. package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
  89. package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
  90. package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
  91. package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
  92. package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
  93. package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
  94. package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
  95. package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
  96. package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
  97. package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
  98. package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
  99. package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
  100. package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
  101. package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
  102. package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
  103. package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
  104. package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
  105. package/dist/src/control-plane/sessions/pi/translator.js +100 -0
  106. package/dist/src/control-plane/sessions/request.js +164 -0
  107. package/dist/src/control-plane/sessions/resources.js +63 -0
  108. package/dist/src/control-plane/sessions/routes.js +88 -0
  109. package/dist/src/control-plane/sessions/serialize.js +24 -0
  110. package/dist/src/control-plane/sessions/service.js +733 -0
  111. package/dist/src/control-plane/sessions/store.js +538 -0
  112. package/dist/src/control-plane/sessions/types.js +1 -0
  113. package/dist/src/control-plane/skills/archive.js +169 -0
  114. package/dist/src/control-plane/skills/routes.js +41 -0
  115. package/dist/src/control-plane/skills/service.js +29 -0
  116. package/dist/src/control-plane/skills/store.js +233 -0
  117. package/dist/src/control-plane/skills/types.js +5 -0
  118. package/dist/src/control-plane/sqlite-transaction.js +15 -0
  119. package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
  120. package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
  121. package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
  122. package/dist/src/control-plane/vaults/routes.js +66 -0
  123. package/dist/src/control-plane/vaults/service.js +553 -0
  124. package/dist/src/control-plane/vaults/store.js +691 -0
  125. package/dist/src/control-plane/vaults/types.js +1 -0
  126. package/dist/src/control-plane/wake-loop.js +65 -0
  127. package/dist/src/control-plane/wiring.js +92 -0
  128. package/dist/src/control-plane/workspace.js +4 -0
  129. package/dist/src/control-plane/workspaces/store.js +158 -0
  130. package/dist/src/egress-proxy-main.js +103 -0
  131. package/dist/src/main.js +124 -0
  132. package/dist/src/types/agents.js +1 -0
  133. package/dist/src/types/common.js +1 -0
  134. package/dist/src/types/environments.js +1 -0
  135. package/dist/src/types/events.js +121 -0
  136. package/dist/src/types/files.js +1 -0
  137. package/dist/src/types/json.js +19 -0
  138. package/dist/src/types/sessions.js +1 -0
  139. package/dist/ui/managed-agents-console/README.md +52 -0
  140. package/dist/ui/managed-agents-console/docs/agents.md +23 -0
  141. package/dist/ui/managed-agents-console/docs/console.md +23 -0
  142. package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
  143. package/dist/ui/managed-agents-console/docs/environments.md +27 -0
  144. package/dist/ui/managed-agents-console/docs/events.md +19 -0
  145. package/dist/ui/managed-agents-console/docs/files.md +17 -0
  146. package/dist/ui/managed-agents-console/docs/github.md +11 -0
  147. package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
  148. package/dist/ui/managed-agents-console/docs/memory.md +11 -0
  149. package/dist/ui/managed-agents-console/docs/migration.md +17 -0
  150. package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
  151. package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
  152. package/dist/ui/managed-agents-console/docs/overview.md +41 -0
  153. package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
  154. package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
  155. package/dist/ui/managed-agents-console/docs/reference.md +27 -0
  156. package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
  157. package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
  158. package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
  159. package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
  160. package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
  161. package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
  162. package/dist/ui/managed-agents-console/docs/skills.md +15 -0
  163. package/dist/ui/managed-agents-console/docs/tools.md +23 -0
  164. package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
  165. package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
  166. package/dist/ui/managed-agents-console/index.html +47 -0
  167. package/dist/ui/managed-agents-console/serve.mjs +116 -0
  168. package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
  169. package/dist/ui/managed-agents-console/src/api.js +1043 -0
  170. package/dist/ui/managed-agents-console/src/app.jsx +633 -0
  171. package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
  172. package/dist/ui/managed-agents-console/src/console.css +733 -0
  173. package/dist/ui/managed-agents-console/src/data.js +188 -0
  174. package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
  175. package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
  176. package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
  177. package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
  178. package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
  179. package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
  180. package/dist/ui/managed-agents-console/src/sse.js +185 -0
  181. package/dist/ui/managed-agents-console/src/states.jsx +90 -0
  182. package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
  183. package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
  184. package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
  185. package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
  186. package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
  187. package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
  188. package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
  189. package/dist/ui/openapi-docs/VENDOR.md +27 -0
  190. package/dist/ui/openapi-docs/index.html +16 -0
  191. package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
  192. package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
  193. package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
  194. package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
  195. package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
  196. package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
  197. package/package.json +53 -0
@@ -0,0 +1,633 @@
1
+ // app.jsx — root: sessions list, routing, theme tweaks. Mounts everything.
2
+ const { useState, useEffect } = React;
3
+
4
+ function SessionsList({ sessions, openSession, onCreate, dataState = 'loaded', readOnly = false }) {
5
+ const loading = dataState === 'loading';
6
+ const error = dataState === 'error';
7
+ const partial = dataState === 'partial';
8
+ const empty = dataState === 'empty' || sessions.length === 0;
9
+ return (
10
+ <div className="main-scroll scroll fade-in">
11
+ <PageHead title="Sessions" sub="Trace and debug Managed Agents sessions."
12
+ action="Create session" onAction={onCreate} readOnly={readOnly}
13
+ endpoint="POST /v1/sessions" />
14
+ <div className="toolbar">
15
+ <Field wide placeholder="Search by session ID" />
16
+ <Select label="Created" value="All time" />
17
+ <Select label="Agent" value="All" w={120} />
18
+ <Select label="Status" value="Active" w={120} />
19
+ </div>
20
+ {partial && <PartialNotice resource="sessions" />}
21
+ {loading ? <SkeletonTable rows={7} cols={[140, 'grow', 'pill', 160, 64]} />
22
+ : error ? <ErrorState resource="sessions" onRetry={() => {}} />
23
+ : empty ? <EmptyState icon="activity" title="No sessions yet"
24
+ message="Sessions appear here as agents run. Create one to start a local Managed Agents session."
25
+ actionLabel={!readOnly ? "Create session" : null} onAction={onCreate} />
26
+ : <>
27
+ <div className="panel">
28
+ <div className="thead">
29
+ <span className="th" style={{ width:15 }}><span className="checkbox" /></span>
30
+ <span className="th" style={{ width:140 }}>ID</span>
31
+ <span className="th grow">Name</span>
32
+ <span className="th" style={{ width:96 }}>Status</span>
33
+ <span className="th" style={{ width:200 }}>Agent</span>
34
+ <span className="th" style={{ width:64 }}>Created</span>
35
+ <span className="th" style={{ width:20 }} />
36
+ </div>
37
+ {sessions.map((s) => (
38
+ <div className={'trow' + (s.focus ? '' : '')} key={s.id} onClick={() => openSession(s)}>
39
+ <span className="td" style={{ width:15 }}><span className="checkbox" /></span>
40
+ <span className="td mono" style={{ width:140, fontSize:12, color:'var(--soft)' }}>{s.short}</span>
41
+ <span className="td grow ell cell-strong">{s.title}</span>
42
+ <span className="td" style={{ width:96, display:'flex', gap:6, flexWrap:'wrap' }}>
43
+ <St k={s.status} />{(s.requiresAction || s.confirm) && <NeedsAction />}
44
+ </span>
45
+ <span className="td" style={{ width:200 }}><Pill icon="bot">{s.agent}</Pill></span>
46
+ <span className="td mono" style={{ width:64, color:'var(--faint)' }}>{s.created}</span>
47
+ <span className="td" style={{ width:20 }}><Kebab /></span>
48
+ </div>
49
+ ))}
50
+ </div>
51
+ <Pager />
52
+ </>}
53
+ </div>
54
+ );
55
+ }
56
+
57
+ const UI_FONTS = {
58
+ geist: "'Geist', ui-sans-serif, system-ui, sans-serif",
59
+ plex: "'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif",
60
+ };
61
+ const MONO_FONTS = {
62
+ geist: "'Geist Mono', ui-monospace, monospace",
63
+ jetbrains: "'JetBrains Mono', ui-monospace, monospace",
64
+ plex: "'IBM Plex Mono', ui-monospace, monospace",
65
+ };
66
+
67
+ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
68
+ "accent": "#eab44c",
69
+ "density": "comfortable",
70
+ "darker": false,
71
+ "layout": "split",
72
+ "uiFont": "geist",
73
+ "mono": "geist",
74
+ "dataState": "loaded"
75
+ }/*EDITMODE-END*/;
76
+
77
+ function routeHash(route) {
78
+ if (route.name === 'session') return `#session=${encodeURIComponent(route.session.id)}`;
79
+ if (route.name === 'agent') return `#agent=${encodeURIComponent(route.agent.id)}`;
80
+ if (route.name === 'start') return '#start';
81
+ if (route.name === 'agents') return '#agents';
82
+ if (route.name === 'environments') return '#environments';
83
+ if (route.name === 'files') return '#files';
84
+ if (route.name === 'skills') return '#skills';
85
+ if (route.name === 'vaults') return route.vaultId ? `#vault=${encodeURIComponent(route.vaultId)}` : '#vaults';
86
+ if (route.name === 'credentialHealth') return `#credential-health=${encodeURIComponent(route.workspaceId)}`;
87
+ if (route.name === 'admin') return '#admin';
88
+ if (route.name === 'documentation') return `#docs=${encodeURIComponent(route.page || 'overview')}`;
89
+ return '#sessions';
90
+ }
91
+
92
+ function writeRouteHash(route) {
93
+ const url = new URL(window.location.href);
94
+ url.searchParams.delete('session_id');
95
+ url.searchParams.delete('agent_id');
96
+ url.hash = routeHash(route);
97
+ const next = `${url.pathname}${url.search}${url.hash}`;
98
+ const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;
99
+ if (current !== next) window.history.replaceState(null, '', next);
100
+ }
101
+
102
+ function readRouteTarget(sessions, agents) {
103
+ const params = new URLSearchParams(window.location.search);
104
+ const rawHash = window.location.hash.replace(/^#/, '');
105
+ const hashParams = new URLSearchParams(rawHash.includes('=') ? rawHash : '');
106
+ const sessionId = params.get('session_id') || hashParams.get('session');
107
+ if (sessionId) {
108
+ const session = sessions.find((item) => item.id === sessionId || item.short === sessionId);
109
+ if (session) return { name:'session', session };
110
+ }
111
+ const agentId = params.get('agent_id') || hashParams.get('agent');
112
+ if (agentId) {
113
+ const agent = agents.find((item) => item.id === agentId || item.short === agentId);
114
+ if (agent) return { name:'agent', agent };
115
+ }
116
+ if (rawHash === 'start') return { name:'start' };
117
+ if (rawHash === 'agents') return { name:'agents' };
118
+ if (rawHash === 'environments') return { name:'environments' };
119
+ if (rawHash === 'files') return { name:'files' };
120
+ if (rawHash === 'skills') return { name:'skills' };
121
+ if (rawHash === 'vaults') return { name:'vaults' };
122
+ if (hashParams.get('vault')) return { name:'vaults', vaultId:hashParams.get('vault') };
123
+ if (hashParams.get('credential-health')) return { name:'credentialHealth', workspaceId:hashParams.get('credential-health') };
124
+ if (rawHash === 'docs' || rawHash === 'documentation') return { name:'documentation', page:'overview' };
125
+ if (hashParams.get('docs')) return { name:'documentation', page:hashParams.get('docs') };
126
+ if (hashParams.get('documentation')) return { name:'documentation', page:'overview' };
127
+ return null;
128
+ }
129
+
130
+ function App() {
131
+ const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
132
+ const demoMode = new URLSearchParams(window.location.search).get('mode') === 'demo';
133
+ const [route, setRoute] = useState(() => readRouteTarget([], []) || { name:'start' });
134
+ const [sessions, setSessions] = useState(SESSIONS);
135
+ const [agents, setAgents] = useState(AGENTS);
136
+ const [environments, setEnvironments] = useState(ENVIRONMENTS);
137
+ const [files, setFiles] = useState(FILES);
138
+ const [vaults, setVaults] = useState([]);
139
+ const [vaultRefresh, setVaultRefresh] = useState(0);
140
+ const [skills, setSkills] = useState([]);
141
+ const [networkingCatalog, setNetworkingCatalog] = useState({
142
+ deployment:{ provider:null, egress_supported:false, reason:"Networking capability has not been loaded." },
143
+ presets:[],
144
+ custom:{ https_only:true, wildcard_matches_bare_domain:false },
145
+ });
146
+ const [models, setModels] = useState(demoMode ? MODELS.map((id) => ({
147
+ type:'model', provider:'anthropic', id, name:id,
148
+ credentials_configured:true, default:id === MODELS[0],
149
+ })) : []);
150
+ const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
151
+ const [apiState, setApiState] = useState({ state:'loading', mode: demoMode ? 'demo' : 'api', error:null, warnings:[] });
152
+ const [modal, setModal] = useState(null); // { kind:'session', presetAgent } | { kind:'agent' } | { kind:'environment' }
153
+ const [createdEnvironmentId, setCreatedEnvironmentId] = useState(null);
154
+ // Raw credentials are used only for the login exchange. After that the
155
+ // server holds a revocable, opaque HttpOnly console session and page reloads
156
+ // restore it without exposing any key to browser storage or JavaScript.
157
+ const [auth, setAuth] = useState({ phase: demoMode ? 'ready' : 'boot', admin:false, workspace:null, error:null, busy:false });
158
+
159
+ useEffect(() => {
160
+ const r = document.documentElement;
161
+ r.classList.toggle('darker', !!t.darker);
162
+ r.classList.toggle('compact', t.density === 'compact');
163
+ r.style.setProperty('--accent', t.accent);
164
+ r.style.setProperty('--font', UI_FONTS[t.uiFont] || UI_FONTS.geist);
165
+ r.style.setProperty('--mono', MONO_FONTS[t.mono] || MONO_FONTS.geist);
166
+ }, [t]);
167
+
168
+ const loadLiveData = () => OmaConsoleApi.loadConsoleData()
169
+ .then((data) => {
170
+ const linkedAgents = linkSessionsToAgents(data.agents, data.sessions);
171
+ setAgents(linkedAgents);
172
+ setSessions(data.sessions);
173
+ setEnvironments(data.environments);
174
+ setFiles(data.files);
175
+ setVaults(data.vaults);
176
+ setSkills(data.skills);
177
+ setNetworkingCatalog(data.networkingCatalog);
178
+ setModels(data.models);
179
+ setApiState({ state:'loaded', mode:'api', error:null, warnings:data.warnings || [] });
180
+ setWorkspaceLoaded(true);
181
+ const target = readRouteTarget(data.sessions, linkedAgents);
182
+ if (target?.name === 'session') openSession(target.session, 'api');
183
+ else if (target) setRoute(target);
184
+ });
185
+
186
+ useEffect(() => {
187
+ let alive = true;
188
+ if (demoMode) {
189
+ setApiState({ state:'loaded', mode:'demo', error:null, warnings:[] });
190
+ const target = readRouteTarget(SESSIONS, AGENTS);
191
+ if (target) setRoute(target);
192
+ return () => { alive = false; };
193
+ }
194
+ OmaConsoleApi.getConsoleAuthStatus()
195
+ .then((status) => {
196
+ if (!alive) return undefined;
197
+ setAuth({ phase: status.workspace || !status.auth_required || status.admin ? 'ready' : 'login', admin:status.admin, workspace:status.workspace, error:null, busy:false });
198
+ if (!status.workspace && status.auth_required) {
199
+ setApiState({ state:'loaded', mode:'api', error:null, warnings:[] });
200
+ return undefined;
201
+ }
202
+ return loadLiveData();
203
+ })
204
+ // When the server requires authentication but this browser has no
205
+ // console session, the preceding branch intentionally returns
206
+ // `undefined`. Keep the login screen visible instead of immediately
207
+ // overwriting it with the ready/no-workspace state.
208
+ .then((loaded) => { if (alive && loaded !== undefined) setAuth((a) => ({ ...a, phase:'ready' })); })
209
+ .catch((error) => {
210
+ if (!alive) return;
211
+ if (error.status === 401) {
212
+ // Server requires a key: ask for one instead of quietly showing
213
+ // bundled demo data behind a login wall.
214
+ setAuth((a) => ({ ...a, phase:'login' }));
215
+ return;
216
+ }
217
+ setAgents([]);
218
+ setSessions([]);
219
+ setEnvironments([]);
220
+ setFiles([]);
221
+ setVaults([]);
222
+ setSkills([]);
223
+ setNetworkingCatalog({
224
+ deployment: { provider:null, egress_supported:false, reason:"Networking capability failed to load." },
225
+ presets:[],
226
+ custom:{ https_only:true, wildcard_matches_bare_domain:false },
227
+ });
228
+ setModels([]);
229
+ setWorkspaceLoaded(false);
230
+ setApiState({ state:'error', mode:'error', error, warnings:[] });
231
+ setAuth((a) => ({ ...a, phase:'ready' }));
232
+ });
233
+ return () => { alive = false; };
234
+ }, [demoMode]);
235
+
236
+ const adminLogin = (key) => {
237
+ setAuth((a) => ({ ...a, busy:true, error:null }));
238
+ OmaConsoleApi.loginConsoleAdmin(key)
239
+ .then(() => {
240
+ setAuth((a) => ({ ...a, phase:'ready', admin:true, error:null, busy:false }));
241
+ setApiState({ state:'loaded', mode:'api', error:null, warnings:[] });
242
+ setRoute({ name:'admin' });
243
+ })
244
+ .catch((error) => {
245
+ setAuth((a) => ({ ...a, busy:false, error: error.status === 401
246
+ ? 'The server rejected that admin key.'
247
+ : error.message }));
248
+ });
249
+ };
250
+ const workspaceLogin = (key) => {
251
+ setAuth((a) => ({ ...a, busy:true, error:null }));
252
+ OmaConsoleApi.loginConsoleWorkspace(key)
253
+ .then((result) => loadLiveData().then(() => result))
254
+ .then((result) => {
255
+ setAuth((a) => ({ ...a, phase:'ready', workspace:result.workspace, busy:false, error:null }));
256
+ })
257
+ .catch((error) => {
258
+ setAuth((a) => ({ ...a, busy:false, error: error.status === 401
259
+ ? 'The server rejected that workspace key.'
260
+ : error.message }));
261
+ });
262
+ };
263
+ const browseAsWorkspace = (plaintextKey) => {
264
+ OmaConsoleApi.loginConsoleWorkspace(plaintextKey)
265
+ .then((result) => loadLiveData().then(() => result))
266
+ .then((result) => {
267
+ setAuth((a) => ({ ...a, workspace:result.workspace, phase:'ready', error:null }));
268
+ setRoute({ name:'sessions' });
269
+ writeRouteHash({ name:'sessions' });
270
+ })
271
+ .catch((error) => {
272
+ // A just-minted key failing to browse is worth a loud re-login, not
273
+ // a silently ignored click.
274
+ setAuth((a) => ({ ...a, phase:'login', error:
275
+ `Browsing with the minted key failed (${error.status ?? error.message}). Enter a key to continue.` }));
276
+ });
277
+ };
278
+ const reauth = () => setAuth((a) => ({ ...a, phase:'login', admin:false, workspace:null, error:'Your console session expired. Enter a key to continue.' }));
279
+ const workspaceReauth = () => {
280
+ setAuth((a) => ({ ...a, phase:'login', admin:false, workspace:null, error:'Your workspace session expired. Enter a key to continue.' }));
281
+ };
282
+ const selectWorkspace = (workspaceId) => {
283
+ OmaConsoleApi.selectConsoleWorkspace(workspaceId)
284
+ .then((result) => loadLiveData().then(() => result))
285
+ .then((result) => {
286
+ setAuth((a) => ({ ...a, workspace:result.workspace, phase:'ready', error:null }));
287
+ go('start');
288
+ })
289
+ .catch((error) => {
290
+ if (error.status === 401) reauth();
291
+ });
292
+ };
293
+ const signOut = () => {
294
+ OmaConsoleApi.logoutConsole().catch(() => undefined).finally(() => {
295
+ setWorkspaceLoaded(false);
296
+ setAuth({ phase:'login', admin:false, workspace:null, error:null, busy:false });
297
+ setRoute({ name:'start' });
298
+ });
299
+ };
300
+
301
+ const go = (name) => {
302
+ const next = name === 'documentation' ? { name, page:'overview' } : { name };
303
+ setRoute(next);
304
+ writeRouteHash(next);
305
+ };
306
+ const openDocumentation = (page) => {
307
+ const next = { name:'documentation', page };
308
+ setRoute(next);
309
+ writeRouteHash(next);
310
+ };
311
+ const openSession = (session, mode = apiState.mode) => {
312
+ writeRouteHash({ name:'session', session });
313
+ setRoute({ name:'session', session: { ...session, loadingEvents:true } });
314
+ if (mode !== 'api') {
315
+ setRoute({ name:'session', session });
316
+ return;
317
+ }
318
+ OmaConsoleApi.hydrateSession(session)
319
+ .then((hydrated) => {
320
+ setRoute((current) => current.name === 'session' && current.session.id === session.id
321
+ ? { name:'session', session: hydrated }
322
+ : current);
323
+ })
324
+ .catch((error) => {
325
+ setRoute((current) => current.name === 'session' && current.session.id === session.id
326
+ ? { name:'session', session: { ...session, eventError:error } }
327
+ : current);
328
+ });
329
+ };
330
+ const openAgent = (agent) => {
331
+ const next = { name:'agent', agent };
332
+ setRoute(next);
333
+ writeRouteHash(next);
334
+ };
335
+ const mutationReadOnly = apiState.state !== 'loaded'
336
+ || (apiState.mode !== 'api' && apiState.mode !== 'demo');
337
+ const lifecycleReadOnly = mutationReadOnly;
338
+
339
+ const createSession = (preset) => {
340
+ if (mutationReadOnly) return;
341
+ setModal({ kind:'session', presetAgent: preset });
342
+ };
343
+ const createAgent = () => {
344
+ if (mutationReadOnly) return;
345
+ setModal({ kind:'agent' });
346
+ };
347
+ const createEnvironment = () => {
348
+ if (apiState.mode !== 'api') return;
349
+ setModal({ kind:'environment' });
350
+ };
351
+ const createVault = () => {
352
+ if (mutationReadOnly) return;
353
+ setModal({ kind:'vault' });
354
+ };
355
+ const createCredential = (vault) => {
356
+ if (mutationReadOnly) return;
357
+ setModal({ kind:'credential', vault });
358
+ };
359
+ const createSkill = () => {
360
+ if (mutationReadOnly) return;
361
+ setModal({ kind:'skill' });
362
+ };
363
+
364
+ const onSessionCreated = (s) => {
365
+ if (mutationReadOnly) return;
366
+ setSessions((prev) => [s, ...prev]);
367
+ setModal(null);
368
+ openSession(s);
369
+ };
370
+ const onAgentCreated = (a) => {
371
+ if (mutationReadOnly) return;
372
+ setAgents((prev) => [a, ...prev]);
373
+ setModal(null);
374
+ openAgent(a);
375
+ };
376
+ const onEnvironmentCreated = (environment) => {
377
+ setEnvironments((prev) => [environment, ...prev]);
378
+ setCreatedEnvironmentId(environment.id);
379
+ setModal(null);
380
+ const next = { name:'environments' };
381
+ setRoute(next);
382
+ writeRouteHash(next);
383
+ };
384
+ const onVaultCreated = (vault) => {
385
+ setVaults((prev) => [vault, ...prev.filter((item) => item.id !== vault.id)]);
386
+ setVaultRefresh((current) => current + 1);
387
+ setModal(null);
388
+ const next = { name:'vaults', vaultId:vault.id };
389
+ setRoute(next);
390
+ writeRouteHash(next);
391
+ };
392
+ const onCredentialCreated = (credential) => {
393
+ setVaultRefresh((current) => current + 1);
394
+ setModal(null);
395
+ const next = { name:'vaults', vaultId:credential.vault_id };
396
+ setRoute(next);
397
+ writeRouteHash(next);
398
+ };
399
+ const onSkillCreated = (skill) => {
400
+ setSkills((prev) => [skill, ...prev]);
401
+ setModal(null);
402
+ };
403
+ const onSessionStateChange = (sessionId, patch) => {
404
+ setSessions((current) => current.map((session) => session.id === sessionId
405
+ ? { ...session, ...patch }
406
+ : session));
407
+ };
408
+ const refreshOpenSession = (session) => {
409
+ if (apiState.mode !== 'api') return;
410
+ OmaConsoleApi.hydrateSession(session)
411
+ .then((hydrated) => {
412
+ setRoute((current) => current.name === 'session' && current.session.id === session.id
413
+ ? { name:'session', session:hydrated }
414
+ : current);
415
+ })
416
+ .catch((error) => setRoute((current) => current.name === 'session' && current.session.id === session.id
417
+ ? { name:'session', session:{ ...current.session, refreshError:error } }
418
+ : current));
419
+ };
420
+
421
+ const archiveSession = async (s) => {
422
+ if (lifecycleReadOnly) return;
423
+ if (apiState.mode === 'api') {
424
+ try {
425
+ await OmaConsoleApi.archiveSession(s.id);
426
+ } catch (error) {
427
+ if (error.status === 401) workspaceReauth();
428
+ throw error;
429
+ }
430
+ }
431
+ setSessions((prev) => prev.map((x) => x.id === s.id ? { ...x, status:'archived' } : x));
432
+ };
433
+ const deleteSession = async (s) => {
434
+ if (lifecycleReadOnly) return;
435
+ if (apiState.mode === 'api') {
436
+ try {
437
+ await OmaConsoleApi.deleteSession(s.id);
438
+ } catch (error) {
439
+ if (error.status === 401) workspaceReauth();
440
+ throw error;
441
+ }
442
+ }
443
+ setSessions((prev) => prev.filter((x) => x.id !== s.id));
444
+ go('sessions');
445
+ };
446
+ const archiveAgent = async (a) => {
447
+ if (lifecycleReadOnly) return;
448
+ let upd = { ...a, status:'archived' };
449
+ if (apiState.mode === 'api') {
450
+ try {
451
+ upd = { ...await OmaConsoleApi.archiveAgent(a.id), sessions:a.sessions || [] };
452
+ } catch (error) {
453
+ if (error.status === 401) workspaceReauth();
454
+ throw error;
455
+ }
456
+ }
457
+ setAgents((prev) => prev.map((x) => x.id === a.id ? upd : x));
458
+ const next = { name:'agent', agent:upd };
459
+ setRoute(next);
460
+ writeRouteHash(next);
461
+ };
462
+ const archiveEnvironment = async (environment) => {
463
+ if (lifecycleReadOnly) return;
464
+ try {
465
+ const updated = await OmaConsoleApi.archiveEnvironment(environment.id);
466
+ setEnvironments((prev) => prev.map((item) => item.id === environment.id ? updated : item));
467
+ } catch (error) {
468
+ if (error.status === 401) workspaceReauth();
469
+ throw error;
470
+ }
471
+ };
472
+ const deleteEnvironment = async (environment) => {
473
+ if (lifecycleReadOnly) return;
474
+ try {
475
+ await OmaConsoleApi.deleteEnvironment(environment.id);
476
+ setEnvironments((prev) => prev.filter((item) => item.id !== environment.id));
477
+ } catch (error) {
478
+ if (error.status === 401) workspaceReauth();
479
+ throw error;
480
+ }
481
+ };
482
+ const updateAgentToolPermission = async (a, policy) => {
483
+ if (mutationReadOnly) return;
484
+ let upd;
485
+ try {
486
+ upd = apiState.mode === 'api'
487
+ ? { ...await OmaConsoleApi.updateAgentToolPermission(a, policy), sessions:a.sessions || [] }
488
+ : {
489
+ ...a,
490
+ version:`v${(a.apiVersion || Number(String(a.version).replace(/^v/, '')) || 1) + 1}`,
491
+ apiVersion:(a.apiVersion || 1) + 1,
492
+ toolPermission:policy === 'always_allow' ? 'Always allow' : 'Ask before use',
493
+ };
494
+ } catch (error) {
495
+ if (error.status === 401) workspaceReauth();
496
+ throw error;
497
+ }
498
+ setAgents((prev) => prev.map((x) => x.id === a.id ? upd : x));
499
+ const next = { name:'agent', agent:upd };
500
+ setRoute(next);
501
+ writeRouteHash(next);
502
+ };
503
+
504
+ if (auth.phase === 'login' && route.name !== 'documentation') {
505
+ return (
506
+ <div className="app">
507
+ <Sidebar route={route.name} go={go} workspace={auth.workspace} admin={auth.admin}
508
+ onSelectWorkspace={selectWorkspace} onSwitchWorkspace={workspaceReauth} onSignOut={signOut} />
509
+ <main className="main">
510
+ <LoginView onAdminLogin={adminLogin} onWorkspaceLogin={workspaceLogin}
511
+ error={auth.error} busy={auth.busy} />
512
+ </main>
513
+ </div>
514
+ );
515
+ }
516
+
517
+ let view;
518
+ const dataState = apiState.state === 'loading' ? 'loading' : t.dataState;
519
+ // Admin-only sessions never loaded /v1: the state still holds the bundled
520
+ // demo rows, which must not render as if they were live tenant data.
521
+ const needsWorkspaceKey = apiState.state !== 'loading' && apiState.mode === 'api'
522
+ && !demoMode && !workspaceLoaded && route.name !== 'admin' && route.name !== 'credentialHealth' && route.name !== 'documentation';
523
+ if (route.name === 'documentation') view = <DocsView page={route.page} onNavigate={openDocumentation} goConsole={go} />;
524
+ else if (apiState.state === 'error') view = (
525
+ <div className="main-scroll scroll fade-in">
526
+ <PageHead title="Live API unavailable" sub="The console will not substitute demo data for a failed live server." />
527
+ <ErrorState resource="workspace data" onRetry={() => {
528
+ setApiState({ state:'loading', mode:'api', error:null, warnings:[] });
529
+ loadLiveData().catch((error) => {
530
+ if (error.status === 401) {
531
+ setAuth((current) => ({ ...current, phase:'login', error:'Session expired — enter a workspace key to retry.' }));
532
+ } else {
533
+ setApiState({ state:'error', mode:'error', error, warnings:[] });
534
+ }
535
+ });
536
+ }} />
537
+ </div>
538
+ );
539
+ else if (needsWorkspaceKey) view = (
540
+ <div className="main-scroll scroll fade-in">
541
+ <PageHead title="No workspace selected" sub="Browsing /v1 needs a workspace key." />
542
+ <EmptyState icon="database" title="Connect a workspace"
543
+ message="Mint a key in Admin and choose “Browse as this workspace”, or reload and enter a workspace key."
544
+ actionLabel={auth.admin ? 'Open Admin' : null} onAction={() => go('admin')} />
545
+ </div>
546
+ );
547
+ else if (route.name === 'admin') view = <AdminPanel onBrowseWorkspace={browseAsWorkspace} onReauth={reauth} onCredentialHealth={(workspaceId) => { const next = { name:'credentialHealth', workspaceId }; setRoute(next); writeRouteHash(next); }} />;
548
+ else if (route.name === 'credentialHealth') view = <CredentialHealthView workspaceId={route.workspaceId} onBack={() => go('admin')} onReauth={reauth} />;
549
+ else if (route.name === 'start') view = <ReadinessView agents={agents} environments={environments} models={models} mode={apiState.mode} workspaceLoaded={workspaceLoaded || demoMode} go={go} onCreateAgent={createAgent} onCreateEnvironment={createEnvironment} onCreateSession={() => createSession(null)} />;
550
+ else if (route.name === 'sessions') view = <SessionsList sessions={sessions} openSession={openSession} onCreate={() => createSession(null)} dataState={dataState} readOnly={mutationReadOnly} />;
551
+ else if (route.name === 'session') view = <SessionDetail session={route.session} layout={t.layout} go={go} onArchive={archiveSession} onDelete={deleteSession} onSessionStateChange={onSessionStateChange} onRefreshSession={refreshOpenSession} dataState={dataState} apiMode={apiState.mode} readOnly={mutationReadOnly} lifecycleReadOnly={lifecycleReadOnly} onAuthExpired={workspaceReauth} />;
552
+ else if (route.name === 'agents') view = <AgentsList agents={agents} openAgent={openAgent} onCreate={createAgent} dataState={dataState} readOnly={mutationReadOnly} />;
553
+ else if (route.name === 'agent') view = <AgentDetail agent={route.agent} go={go} onCreateSession={() => createSession(route.agent)} onArchive={() => archiveAgent(route.agent)} onUpdateToolPermission={(policy) => updateAgentToolPermission(route.agent, policy)} createSessionReadOnly={mutationReadOnly} archiveReadOnly={lifecycleReadOnly} />;
554
+ else if (route.name === 'environments') view = <EnvironmentsView environments={environments} mode={apiState.mode} dataState={dataState} onCreate={createEnvironment} createdEnvironmentId={createdEnvironmentId} onArchive={archiveEnvironment} onDelete={deleteEnvironment} />;
555
+ else if (route.name === 'files') view = <FilesView files={files} dataState={dataState} readOnly={true} />;
556
+ else if (route.name === 'vaults') view = <VaultsView key={`${route.vaultId || 'vaults'}:${vaultRefresh}`} mode={apiState.mode} initialVaultId={route.vaultId} onCreate={createVault} onCreateCredential={createCredential} readOnly={mutationReadOnly} onOpenVault={(vaultId) => { const next = { name:'vaults', vaultId }; setRoute(next); writeRouteHash(next); }} onBackToVaults={() => go('vaults')} />;
557
+ else if (route.name === 'skills') view = <SkillsView skills={skills} mode={apiState.mode} onCreate={createSkill} readOnly={mutationReadOnly} />;
558
+
559
+ if (route.name === 'documentation') return <div className="docs-app">{view}</div>;
560
+
561
+ return (
562
+ <div className="app">
563
+ <Sidebar route={route.name} go={go} showAdmin={auth.admin} workspace={auth.workspace} admin={auth.admin}
564
+ onSelectWorkspace={selectWorkspace} onSwitchWorkspace={workspaceReauth} onSignOut={signOut} />
565
+ <main className="main">
566
+ {route.name !== 'documentation' && apiState.state !== 'loading' && <ModeBar mode={apiState.mode} warnings={apiState.warnings} />}
567
+ {view}
568
+ </main>
569
+
570
+ {modal && modal.kind === 'session' &&
571
+ <CreateSession agents={agents} environments={environments} vaults={vaults} presetAgent={modal.presetAgent} onClose={() => setModal(null)} onCreate={onSessionCreated} onAuthExpired={workspaceReauth} apiMode={apiState.mode} />}
572
+ {modal && modal.kind === 'agent' &&
573
+ <CreateAgent models={models} skills={skills} onClose={() => setModal(null)} onCreate={onAgentCreated} onAuthExpired={workspaceReauth} apiMode={apiState.mode} />}
574
+ {modal && modal.kind === 'environment' &&
575
+ <CreateEnvironmentModal mode={apiState.mode}
576
+ networkingCatalog={networkingCatalog}
577
+ onClose={() => setModal(null)} onCreated={onEnvironmentCreated}
578
+ onAuthExpired={workspaceReauth} />}
579
+ {modal && modal.kind === 'vault' &&
580
+ <CreateVaultModal onClose={() => setModal(null)} onCreated={onVaultCreated} onAuthExpired={workspaceReauth} apiMode={apiState.mode} />}
581
+ {modal && modal.kind === 'credential' &&
582
+ <CreateVaultCredentialModal vault={modal.vault} onClose={() => setModal(null)} onCreated={onCredentialCreated} onAuthExpired={workspaceReauth} apiMode={apiState.mode} />}
583
+ {modal && modal.kind === 'skill' &&
584
+ <CreateSkillModal onClose={() => setModal(null)} onCreated={onSkillCreated} onAuthExpired={workspaceReauth} apiMode={apiState.mode} />}
585
+
586
+ <TweaksPanel>
587
+ <TweakSection label="Theme" />
588
+ <TweakColor label="Accent" value={t.accent}
589
+ options={['#eab44c', '#e8855b', '#4cc2bd', '#6ea8e6']}
590
+ onChange={(v) => setTweak('accent', v)} />
591
+ <TweakToggle label="Darker surface" value={t.darker} onChange={(v) => setTweak('darker', v)} />
592
+ <TweakRadio label="Density" value={t.density} options={['compact', 'comfortable']}
593
+ onChange={(v) => setTweak('density', v)} />
594
+
595
+ <TweakSection label="Session detail" />
596
+ <TweakRadio label="Layout" value={t.layout} options={['split', 'threecol']}
597
+ onChange={(v) => setTweak('layout', v)} />
598
+
599
+ <TweakSection label="Data state" />
600
+ <TweakSelect label="Preview" value={t.dataState} options={['loaded', 'loading', 'partial', 'empty', 'error']}
601
+ onChange={(v) => setTweak('dataState', v)} />
602
+
603
+ <TweakSection label="Typeface" />
604
+ <TweakRadio label="UI font" value={t.uiFont} options={['geist', 'plex']}
605
+ onChange={(v) => setTweak('uiFont', v)} />
606
+ <TweakSelect label="Mono" value={t.mono} options={['geist', 'jetbrains', 'plex']}
607
+ onChange={(v) => setTweak('mono', v)} />
608
+
609
+ <TweakSection label="Jump to" />
610
+ <TweakButton label="Open running session (live)" onClick={() => openSession(SESSIONS.find((s) => s.status === 'running'))} />
611
+ <TweakButton label="Open tool-confirmation session" onClick={() => openSession(SESSIONS.find((s) => s.confirm))} />
612
+ <TweakButton label="Open focus session" onClick={() => openSession(SESSIONS.find((s) => s.focus))} />
613
+ </TweaksPanel>
614
+ </div>
615
+ );
616
+ }
617
+
618
+ function linkSessionsToAgents(agents, sessions) {
619
+ const titlesByAgent = new Map();
620
+ for (const session of sessions) {
621
+ const key = session.agentId;
622
+ if (!key) continue;
623
+ const list = titlesByAgent.get(key) || [];
624
+ list.push(session.title);
625
+ titlesByAgent.set(key, list.slice(0, 5));
626
+ }
627
+ return agents.map((agent) => ({
628
+ ...agent,
629
+ sessions: titlesByAgent.get(agent.id) ?? agent.sessions ?? [],
630
+ }));
631
+ }
632
+
633
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);