open-managed-agents 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/dist/scripts/alpha-smoke.mjs +40 -2
- package/dist/scripts/oma-doctor.js +1 -1
- package/dist/scripts/oma-models.js +20 -16
- package/dist/src/control-plane/app.js +75 -4
- package/dist/src/control-plane/events/service.js +82 -8
- package/dist/src/control-plane/models/catalog.js +29 -13
- package/dist/src/control-plane/models/credential-store.js +203 -0
- package/dist/src/control-plane/sessions/pi/mcp/client.js +5 -1
- package/dist/src/control-plane/sessions/pi/runner.js +59 -32
- package/dist/src/control-plane/sessions/pi/sandbox/container-engine.js +32 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +8 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker.js +20 -5
- package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +18 -3
- package/dist/src/control-plane/sessions/pi/sandbox/provider.js +16 -9
- package/dist/src/control-plane/sessions/service.js +5 -0
- package/dist/src/control-plane/vaults/console-mcp-oauth.js +545 -0
- package/dist/src/control-plane/vaults/service.js +4 -1
- package/dist/src/control-plane/vaults/store.js +8 -2
- package/dist/src/main.js +1 -1
- package/dist/ui/managed-agents-console/README.md +22 -15
- package/dist/ui/managed-agents-console/docs/vaults.md +16 -2
- package/dist/ui/managed-agents-console/index.html +4 -0
- package/dist/ui/managed-agents-console/src/api.js +40 -0
- package/dist/ui/managed-agents-console/src/console.css +4 -1
- package/dist/ui/managed-agents-console/src/forms.jsx +79 -16
- package/dist/ui/managed-agents-console/src/session-vaults-data.js +40 -0
- package/dist/ui/managed-agents-console/src/vaults.jsx +104 -9
- package/package.json +15 -7
|
@@ -14,24 +14,31 @@ hosts.
|
|
|
14
14
|
|
|
15
15
|
## Logging in
|
|
16
16
|
|
|
17
|
-
The
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
The normal `oma onboard` path opens the loopback console through a short-lived,
|
|
18
|
+
single-use bootstrap nonce and establishes an opaque HttpOnly browser session;
|
|
19
|
+
no raw workspace key enters the URL or browser storage. A user can also enter a
|
|
20
|
+
workspace key manually when no console session exists. If the server runs with
|
|
21
|
+
`OMA_AUTH_MODE=disabled`, it browses `wrk_default` directly.
|
|
22
|
+
|
|
23
|
+
- **Workspace key** (`oma_…`) — workspace-scoped browsing and the console's
|
|
24
|
+
narrowly allowlisted agent, environment, session, vault, credential, and
|
|
25
|
+
skill workflows, including authenticated event streams and file downloads.
|
|
23
26
|
- **Admin key** (`OMA_ADMIN_KEY`) — everything above plus the Admin panel:
|
|
24
27
|
create workspaces, mint/list/revoke API keys. Minted plaintext is shown
|
|
25
28
|
once, with copy and a "browse as this workspace" shortcut. Setup:
|
|
26
29
|
[dev-deployment.md](../../docs/dev-deployment.md#the-admin-api-and-console-admin-mode).
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
Raw keys are used only for the same-origin login exchange and are never stored
|
|
32
|
+
in `localStorage` or `sessionStorage`. The server returns an opaque HttpOnly
|
|
33
|
+
cookie whose authority is revalidated against key revocation. Reloading keeps
|
|
34
|
+
the console session until it expires, is revoked, or the user signs out.
|
|
31
35
|
|
|
32
|
-
Admin
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
Admin workspace/key CRUD is live. The workspace console also supports the
|
|
37
|
+
reviewed mutation set required for the single-agent flow: agent creation and
|
|
38
|
+
versioning/archive, environment creation/archive/delete, session creation,
|
|
39
|
+
prompt/interrupt/confirmation and idle archive/delete, vault/credential
|
|
40
|
+
creation, MCP validation, and custom-skill upload. Arbitrary `/v1` writes remain
|
|
41
|
+
blocked by narrow client-side capability wrappers and server authorization.
|
|
35
42
|
|
|
36
43
|
## Dev server (optional)
|
|
37
44
|
|
|
@@ -47,6 +54,6 @@ expose it as a shared gateway; it forwards browser request headers to the
|
|
|
47
54
|
configured API. Admin mode needs same-origin `/admin` routes, so use the
|
|
48
55
|
appliance-served `/console` for that.
|
|
49
56
|
|
|
50
|
-
Demo review mode with bundled fake data: append `?mode=demo` to
|
|
51
|
-
|
|
52
|
-
a
|
|
57
|
+
Demo review mode with bundled fake data is explicit: append `?mode=demo` to
|
|
58
|
+
either URL. A failed live API request never falls back to demo data; the console
|
|
59
|
+
clears live rows and renders a retryable error state.
|
|
@@ -6,14 +6,28 @@
|
|
|
6
6
|
|
|
7
7
|
Vaults organize workspace-scoped credentials. The console can show vault and credential metadata and health without rendering secret values. The API supports vault and credential lifecycle operations, including archive and delete where the server permits them.
|
|
8
8
|
|
|
9
|
+
## Connect an OAuth MCP server
|
|
10
|
+
|
|
11
|
+
OAuth connections require MCP support to be enabled on the appliance (`OMA_ENABLE_MCP=true`) and a configured secrets master key. In the console:
|
|
12
|
+
|
|
13
|
+
1. Open **Vaults**, create or select a vault, and choose **Add credential**.
|
|
14
|
+
2. Leave **Connect with OAuth** selected and enter the exact URL used in the agent's `mcp_servers` configuration, such as `https://mcp.notion.com/mcp`.
|
|
15
|
+
3. Choose **Connect** and finish authorization in the provider window.
|
|
16
|
+
|
|
17
|
+
The browser receives an authorization URL and opaque flow status only. Access tokens, refresh tokens, client secrets, and authorization codes are exchanged and encrypted by the appliance; they are never displayed or pasted into the console.
|
|
18
|
+
|
|
19
|
+
This guided path supports URL-based, standards-compliant MCP servers that publish OAuth discovery metadata and either dynamic client registration or an existing reusable registration. A provider that lacks those capabilities produces an explicit unsupported-provider error. OMA does not ship a third-party MCP marketplace.
|
|
20
|
+
|
|
9
21
|
## Use a credential in a session
|
|
10
22
|
|
|
11
23
|
Attach the needed vault at session creation and configure the compatible MCP server on the agent. OMA resolves supported authentication inside the connector path; the sandbox does not receive a browser-visible plaintext secret.
|
|
12
24
|
|
|
13
25
|
## Supported and deferred types
|
|
14
26
|
|
|
15
|
-
OMA supports
|
|
27
|
+
OMA supports static bearer credentials and guided MCP OAuth, including validation and automatic refresh where the provider issues a refresh token. Static bearer remains available for servers that intentionally use a long-lived token. Environment-variable substitution and arbitrary secret injection are not current public credential types.
|
|
16
28
|
|
|
17
29
|
## Rotation and failures
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
Use **Validate** to probe the MCP server, and **Reauthorize** to renew a refreshable OAuth connection without creating a duplicate credential. **Disconnect** archives the OMA credential immediately; it does not revoke the provider-side grant, which must be revoked with the provider when required.
|
|
32
|
+
|
|
33
|
+
If an OAuth refresh or connector call fails, inspect the validation result, session events, and credential health. OMA does not claim that a credential is usable merely because its metadata exists.
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
import * as VaultsData from './src/vaults-data.js';
|
|
23
23
|
window.VaultsData = VaultsData;
|
|
24
24
|
</script>
|
|
25
|
+
<script type="module">
|
|
26
|
+
import * as SessionVaultsData from './src/session-vaults-data.js';
|
|
27
|
+
window.SessionVaultsData = SessionVaultsData;
|
|
28
|
+
</script>
|
|
25
29
|
<script type="text/babel" src="src/icons.jsx"></script>
|
|
26
30
|
<script type="text/babel" src="src/tweaks-panel.jsx"></script>
|
|
27
31
|
<script type="text/babel" src="src/ui.jsx"></script>
|
|
@@ -24,6 +24,7 @@ const ARCHIVE_SESSION_CAPABILITY = Symbol("archive-session");
|
|
|
24
24
|
const DELETE_SESSION_CAPABILITY = Symbol("delete-session");
|
|
25
25
|
const CREATE_VAULT_CAPABILITY = Symbol("create-vault");
|
|
26
26
|
const CREATE_VAULT_CREDENTIAL_CAPABILITY = Symbol("create-vault-credential");
|
|
27
|
+
const ARCHIVE_VAULT_CREDENTIAL_CAPABILITY = Symbol("archive-vault-credential");
|
|
27
28
|
const CREATE_SKILL_CAPABILITY = Symbol("create-skill");
|
|
28
29
|
const CMA_BUILTIN_TOOL_NAMES = [
|
|
29
30
|
"bash",
|
|
@@ -167,6 +168,11 @@ function isAllowedWorkspaceWrite(path, method, capability) {
|
|
|
167
168
|
method === "POST" &&
|
|
168
169
|
/^\/v1\/vaults\/[^/]+\/credentials$/.test(new URL(path, "http://oma.local").pathname)
|
|
169
170
|
) ||
|
|
171
|
+
(
|
|
172
|
+
capability === ARCHIVE_VAULT_CREDENTIAL_CAPABILITY &&
|
|
173
|
+
method === "POST" &&
|
|
174
|
+
/^\/v1\/vaults\/[^/]+\/credentials\/[^/]+\/archive$/.test(new URL(path, "http://oma.local").pathname)
|
|
175
|
+
) ||
|
|
170
176
|
(capability === CREATE_SKILL_CAPABILITY && method === "POST" && path === "/v1/skills") ||
|
|
171
177
|
(
|
|
172
178
|
capability === ARCHIVE_AGENT_CAPABILITY &&
|
|
@@ -266,6 +272,36 @@ export function createVaultCredential(vaultId, body) {
|
|
|
266
272
|
});
|
|
267
273
|
}
|
|
268
274
|
|
|
275
|
+
export function archiveVaultCredential(vaultId, credentialId) {
|
|
276
|
+
const path = `/v1/vaults/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(credentialId)}/archive`;
|
|
277
|
+
return request(path, {
|
|
278
|
+
method: "POST",
|
|
279
|
+
capability: ARCHIVE_VAULT_CREDENTIAL_CAPABILITY,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function startMcpOauthFlow(vaultId, displayName, mcpServerUrl) {
|
|
284
|
+
return request("/console/mcp-oauth/flows", {
|
|
285
|
+
method: "POST",
|
|
286
|
+
body: {
|
|
287
|
+
vault_id: vaultId,
|
|
288
|
+
...(displayName ? { display_name: displayName } : {}),
|
|
289
|
+
mcp_server_url: mcpServerUrl,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function reauthorizeMcpOauthCredential(vaultId, credentialId) {
|
|
295
|
+
return request("/console/mcp-oauth/reauthorize", {
|
|
296
|
+
method: "POST",
|
|
297
|
+
body: { vault_id: vaultId, credential_id: credentialId },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function getMcpOauthFlow(flowId) {
|
|
302
|
+
return request(`/console/mcp-oauth/flows/${encodeURIComponent(flowId)}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
269
305
|
export function listSkills() {
|
|
270
306
|
return fetchCursorPages("/v1/skills");
|
|
271
307
|
}
|
|
@@ -1025,6 +1061,10 @@ if (typeof window !== "undefined") {
|
|
|
1025
1061
|
listVaultCredentials,
|
|
1026
1062
|
createVault,
|
|
1027
1063
|
createVaultCredential,
|
|
1064
|
+
archiveVaultCredential,
|
|
1065
|
+
startMcpOauthFlow,
|
|
1066
|
+
reauthorizeMcpOauthCredential,
|
|
1067
|
+
getMcpOauthFlow,
|
|
1028
1068
|
listSkills,
|
|
1029
1069
|
createSkill,
|
|
1030
1070
|
listWorkspaceCredentialHealth,
|
|
@@ -636,7 +636,7 @@ a.dl { text-decoration:none; }
|
|
|
636
636
|
.modal-head .x { margin-left:auto; }
|
|
637
637
|
.modal-body { padding:18px 20px; display:flex; flex-direction:column; gap:16px; max-height:60vh; overflow:auto; }
|
|
638
638
|
.modal-foot { display:flex; align-items:center; gap:10px; justify-content:flex-end; padding:14px 20px; border-top:1px solid var(--border); }
|
|
639
|
-
.modal-foot .left { margin-right:auto; color:var(--faint); font-size:12px; }
|
|
639
|
+
.modal-foot .left { min-width:0; margin-right:auto; color:var(--faint); font-size:12px; overflow-wrap:anywhere; }
|
|
640
640
|
|
|
641
641
|
.form-row { display:flex; flex-direction:column; gap:7px; }
|
|
642
642
|
.form-row.two { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
|
|
@@ -657,6 +657,7 @@ a.dl { text-decoration:none; }
|
|
|
657
657
|
.tool-toggle { appearance:none; font-family:inherit; display:inline-flex; align-items:center; gap:7px; padding:6px 11px; border-radius:999px; border:1px solid var(--border); background:var(--surface-2); color:var(--soft); font-size:12.5px; font-weight:500; cursor:pointer; transition:.12s; }
|
|
658
658
|
.tool-toggle:hover { border-color:var(--border-2); color:var(--ink); }
|
|
659
659
|
.tool-toggle.on { background:var(--accent-wash); border-color:var(--accent-line); color:var(--accent); }
|
|
660
|
+
.tool-toggle:disabled { cursor:not-allowed; opacity:.45; border-color:var(--border); color:var(--faint); }
|
|
660
661
|
.tool-toggle .chk { width:13px; height:13px; border-radius:4px; border:1.5px solid currentColor; opacity:.55; display:flex; align-items:center; justify-content:center; }
|
|
661
662
|
.tool-toggle.on .chk { background:var(--accent); border-color:var(--accent); opacity:1; }
|
|
662
663
|
.radio-set { display:flex; flex-direction:column; gap:8px; }
|
|
@@ -697,6 +698,8 @@ a.dl { text-decoration:none; }
|
|
|
697
698
|
/* small confirm dialog */
|
|
698
699
|
.modal.sm { max-width:420px; }
|
|
699
700
|
.modal.sm .modal-body { gap:0; }
|
|
701
|
+
.modal.sm .modal-foot { flex-wrap:wrap; }
|
|
702
|
+
.modal.sm .modal-foot .left { flex:1 0 100%; margin-right:0; }
|
|
700
703
|
.confirm-msg { font-size:13.5px; color:var(--soft); line-height:1.6; }
|
|
701
704
|
.confirm-msg b { color:var(--ink); font-weight:600; }
|
|
702
705
|
.modal-head .ic.danger { background:var(--red-wash); color:var(--red); }
|
|
@@ -44,6 +44,7 @@ function CreateSession({ agents = AGENTS, environments = ENVIRONMENTS, vaults =
|
|
|
44
44
|
const [title, setTitle] = useStateF('');
|
|
45
45
|
const [msg, setMsg] = useStateF('');
|
|
46
46
|
const [vaultIds, setVaultIds] = useStateF([]);
|
|
47
|
+
const [vaultCredentialStates, setVaultCredentialStates] = useStateF({});
|
|
47
48
|
const [busy, setBusy] = useStateF(false);
|
|
48
49
|
const [error, setError] = useStateF('');
|
|
49
50
|
const [partial, setPartial] = useStateF(null);
|
|
@@ -53,8 +54,17 @@ function CreateSession({ agents = AGENTS, environments = ENVIRONMENTS, vaults =
|
|
|
53
54
|
const agent = agents.find((a) => a.id === agentId);
|
|
54
55
|
const usingCustom = env === '__custom';
|
|
55
56
|
const finalEnv = usingCustom ? customEnv.trim() : env;
|
|
56
|
-
const valid = !!agentId && !!finalEnv && !busy;
|
|
57
57
|
const live = apiMode === 'api';
|
|
58
|
+
const selectedVaultCompatibility = vaultIds.map((vaultId) => ({
|
|
59
|
+
vault: vaults.find((item) => item.id === vaultId),
|
|
60
|
+
result: window.SessionVaultsData.vaultCompatibility(agent, vaultCredentialStates[vaultId]),
|
|
61
|
+
}));
|
|
62
|
+
const incompatibleVaults = selectedVaultCompatibility.filter(({ result }) => result.status === 'incompatible');
|
|
63
|
+
const unavailableVaults = selectedVaultCompatibility.filter(({ result }) => result.status === 'error');
|
|
64
|
+
const loadingVaults = selectedVaultCompatibility.filter(({ result }) => result.status === 'loading');
|
|
65
|
+
const agentHasMcp = window.SessionVaultsData.agentMcpServerUrls(agent).length > 0;
|
|
66
|
+
const vaultsReady = !live || selectedVaultCompatibility.every(({ result }) => result.compatible);
|
|
67
|
+
const valid = !!agentId && !!finalEnv && !busy && vaultsReady;
|
|
58
68
|
const initialMessageEvent = () => ({
|
|
59
69
|
type: 'user.message',
|
|
60
70
|
content: [{ type: 'text', text: msg.trim() }],
|
|
@@ -69,6 +79,34 @@ function CreateSession({ agents = AGENTS, environments = ENVIRONMENTS, vaults =
|
|
|
69
79
|
created:'Just now', updated:'Just now', dur:'—', tokens:'0 / 0', resources:0,
|
|
70
80
|
});
|
|
71
81
|
|
|
82
|
+
const toggleVault = async (vault) => {
|
|
83
|
+
const selected = vaultIds.includes(vault.id);
|
|
84
|
+
if (selected) {
|
|
85
|
+
setVaultIds((current) => current.filter((id) => id !== vault.id));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (live && !agentHasMcp) return;
|
|
89
|
+
setVaultIds((current) => [...current, vault.id]);
|
|
90
|
+
if (!live || vaultCredentialStates[vault.id]) return;
|
|
91
|
+
setVaultCredentialStates((current) => ({
|
|
92
|
+
...current,
|
|
93
|
+
[vault.id]: { status:'loading', credentials:[] },
|
|
94
|
+
}));
|
|
95
|
+
try {
|
|
96
|
+
const page = await api.listVaultCredentials(vault.id);
|
|
97
|
+
setVaultCredentialStates((current) => ({
|
|
98
|
+
...current,
|
|
99
|
+
[vault.id]: { status:'loaded', credentials:page.data },
|
|
100
|
+
}));
|
|
101
|
+
} catch (credentialError) {
|
|
102
|
+
setVaultCredentialStates((current) => ({
|
|
103
|
+
...current,
|
|
104
|
+
[vault.id]: { status:'error', credentials:[], error:credentialError.message || 'Request failed' },
|
|
105
|
+
}));
|
|
106
|
+
if (credentialError.status === 401 && onAuthExpired) onAuthExpired();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
72
110
|
const submit = async () => {
|
|
73
111
|
if (!valid) return;
|
|
74
112
|
setBusy(true);
|
|
@@ -169,12 +207,27 @@ function CreateSession({ agents = AGENTS, environments = ENVIRONMENTS, vaults =
|
|
|
169
207
|
{vaults.length === 0 ? <div className="field-hint">No active vaults in this workspace.</div>
|
|
170
208
|
: <div className="seg-tools">{vaults.filter((vault) => !vault.archived_at).map((vault) => {
|
|
171
209
|
const selected = vaultIds.includes(vault.id);
|
|
210
|
+
const compatibility = live && vaultCredentialStates[vault.id]
|
|
211
|
+
? window.SessionVaultsData.vaultCompatibility(agent, vaultCredentialStates[vault.id])
|
|
212
|
+
: null;
|
|
213
|
+
const disabled = live && (!agentHasMcp || (!selected && compatibility && !compatibility.compatible));
|
|
214
|
+
const title = !agentHasMcp
|
|
215
|
+
? 'The selected agent declares no MCP servers.'
|
|
216
|
+
: compatibility?.status === 'incompatible'
|
|
217
|
+
? 'No active credential exactly matches this agent’s MCP server URLs.'
|
|
218
|
+
: compatibility?.status === 'error'
|
|
219
|
+
? 'Credential compatibility could not be verified.'
|
|
220
|
+
: undefined;
|
|
172
221
|
return <button type="button" key={vault.id} className={'tool-toggle' + (selected ? ' on' : '')} aria-pressed={selected}
|
|
173
|
-
|
|
222
|
+
disabled={disabled} title={title} onClick={() => toggleVault(vault)}>
|
|
174
223
|
<span className="chk">{selected && <Icon name="checkCircle" size={9} />}</span><span>{vault.display_name}</span>
|
|
175
224
|
</button>;
|
|
176
225
|
})}</div>}
|
|
177
226
|
</Labeled>
|
|
227
|
+
{live && vaults.length > 0 && !agentHasMcp && <div className="inline-warn" role="status"><Icon name="alert" size={14} /><span>The selected agent declares no MCP servers. Choose an MCP-enabled agent before attaching a credential vault.</span></div>}
|
|
228
|
+
{live && loadingVaults.length > 0 && <div className="inline-note" role="status"><Icon name="info" size={14} /><span>Checking whether the selected vault contains a credential matching this agent’s MCP server URLs…</span></div>}
|
|
229
|
+
{live && incompatibleVaults.length > 0 && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{incompatibleVaults.map(({ vault }) => vault?.display_name || vault?.id).join(', ')} has no active credential whose server URL exactly matches this agent. Choose a compatible vault or update the agent’s MCP server URL.</span></div>}
|
|
230
|
+
{live && unavailableVaults.length > 0 && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>Could not verify credentials in {unavailableVaults.map(({ vault }) => vault?.display_name || vault?.id).join(', ')}. Remove the vault or retry after reconnecting the workspace.</span></div>}
|
|
178
231
|
|
|
179
232
|
<Labeled label="Title" htmlFor="create-session-title" opt hint="Defaults to the first message if left blank.">
|
|
180
233
|
<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)} />
|
|
@@ -213,35 +266,45 @@ function CreateVaultModal({ onClose, onCreated, onAuthExpired, apiMode = 'demo',
|
|
|
213
266
|
|
|
214
267
|
function CreateVaultCredentialModal({ vault, onClose, onCreated, onAuthExpired, apiMode = 'demo', api = window.OmaConsoleApi }) {
|
|
215
268
|
const [displayName, setDisplayName] = useStateF('');
|
|
216
|
-
const [type, setType] = useStateF('
|
|
269
|
+
const [type, setType] = useStateF('mcp_oauth');
|
|
217
270
|
const [serverUrl, setServerUrl] = useStateF('');
|
|
218
271
|
const [token, setToken] = useStateF('');
|
|
219
|
-
const [expiresAt, setExpiresAt] = useStateF('');
|
|
220
272
|
const [busy, setBusy] = useStateF(false);
|
|
221
273
|
const [error, setError] = useStateF('');
|
|
222
|
-
const
|
|
274
|
+
const validUrl = /^https?:\/\//.test(serverUrl.trim());
|
|
275
|
+
const valid = validUrl && (type === 'mcp_oauth' || token.trim().length >= 8) && !busy;
|
|
223
276
|
const submit = async () => {
|
|
224
277
|
if (!valid) return;
|
|
225
278
|
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
279
|
try {
|
|
280
|
+
if (type === 'mcp_oauth') {
|
|
281
|
+
if (apiMode !== 'api') {
|
|
282
|
+
onCreated({ id:`vcrd_demo_${Date.now()}`, vault_id:vault.id, display_name:displayName.trim() || null, auth:{ type, mcp_server_url:serverUrl.trim() } });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const status = await runMcpOauthPopup(
|
|
286
|
+
() => api.startMcpOauthFlow(vault.id, displayName.trim(), serverUrl.trim()),
|
|
287
|
+
api,
|
|
288
|
+
);
|
|
289
|
+
onCreated({ id:status.credential_id, vault_id:vault.id });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const auth = { type, mcp_server_url:serverUrl.trim(), token:token.trim() };
|
|
230
293
|
if (apiMode !== 'api') { onCreated({ id:`vcrd_demo_${Date.now()}`, vault_id:vault.id, display_name:displayName.trim() || null, auth }); return; }
|
|
231
294
|
onCreated(await api.createVaultCredential(vault.id, { ...(displayName.trim() ? { display_name:displayName.trim() } : {}), auth }));
|
|
232
295
|
} catch (createError) {
|
|
233
|
-
setError(createError.message || 'Credential
|
|
296
|
+
setError(createError.message || 'Credential connection failed.');
|
|
234
297
|
if (createError.status === 401 && onAuthExpired) onAuthExpired();
|
|
235
298
|
} finally { setBusy(false); }
|
|
236
299
|
};
|
|
237
|
-
|
|
238
|
-
|
|
300
|
+
const endpoint = type === 'mcp_oauth' ? 'POST /console/mcp-oauth/flows' : 'POST /v1/vaults/:id/credentials';
|
|
301
|
+
return <Modal icon="database" title="Add credential" sub={`Authorize an MCP server for ${vault.display_name || vault.displayName}.`} onClose={onClose}
|
|
302
|
+
footer={<><span className="left">Sends to <span className="mono">{endpoint}</span></span><button className="btn" onClick={onClose}>Cancel</button><button className="btn btn-primary" disabled={!valid} onClick={submit}>{busy ? (type === 'mcp_oauth' ? 'Connecting…' : 'Saving…') : (type === 'mcp_oauth' ? 'Connect' : 'Save credential')}</button></>}>
|
|
239
303
|
<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="
|
|
241
|
-
<Labeled label="MCP server URL" htmlFor="credential-server-url" hint="
|
|
242
|
-
|
|
243
|
-
{type === 'mcp_oauth' && <
|
|
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>}
|
|
304
|
+
<Labeled label="Credential type" htmlFor="credential-type"><select id="credential-type" className="selectbox" value={type} onChange={(event) => setType(event.target.value)}><option value="mcp_oauth">Connect with OAuth</option><option value="static_bearer">Static bearer token</option></select></Labeled>
|
|
305
|
+
<Labeled label="MCP server URL" htmlFor="credential-server-url" hint="Use the URL configured on the agent, for example https://mcp.notion.com/mcp."><input id="credential-server-url" className="input mono" placeholder="https://mcp.example.com/mcp" value={serverUrl} onChange={(event) => setServerUrl(event.target.value)} /></Labeled>
|
|
306
|
+
{type === 'static_bearer' && <Labeled label="Bearer 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>}
|
|
307
|
+
{type === 'mcp_oauth' && <div className="field-hint">Connect opens the provider in a new window. OMA stores the resulting credential and refreshes it automatically; the browser never receives an access or refresh token.</div>}
|
|
245
308
|
{error && <div className="inline-warn" role="alert"><Icon name="alert" size={14} /><span>{error}</span></div>}
|
|
246
309
|
</Modal>;
|
|
247
310
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function agentMcpServerUrls(agent) {
|
|
2
|
+
return uniqueStrings(
|
|
3
|
+
(Array.isArray(agent?.mcpServers) ? agent.mcpServers : [])
|
|
4
|
+
.map((server) => server?.url),
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function credentialMcpServerUrls(credentials) {
|
|
9
|
+
return uniqueStrings(
|
|
10
|
+
(Array.isArray(credentials) ? credentials : [])
|
|
11
|
+
.filter((credential) => !credential?.archived_at)
|
|
12
|
+
.map((credential) => credential?.auth?.mcp_server_url),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function vaultCompatibility(agent, credentialState) {
|
|
17
|
+
const agentUrls = agentMcpServerUrls(agent);
|
|
18
|
+
if (agentUrls.length === 0) {
|
|
19
|
+
return { status:"agent_without_mcp", compatible:false, agentUrls, credentialUrls:[] };
|
|
20
|
+
}
|
|
21
|
+
if (!credentialState || credentialState.status === "loading") {
|
|
22
|
+
return { status:"loading", compatible:false, agentUrls, credentialUrls:[] };
|
|
23
|
+
}
|
|
24
|
+
if (credentialState.status === "error") {
|
|
25
|
+
return { status:"error", compatible:false, agentUrls, credentialUrls:[] };
|
|
26
|
+
}
|
|
27
|
+
const credentialUrls = credentialMcpServerUrls(credentialState.credentials);
|
|
28
|
+
const matchingUrls = credentialUrls.filter((url) => agentUrls.includes(url));
|
|
29
|
+
return {
|
|
30
|
+
status: matchingUrls.length > 0 ? "compatible" : "incompatible",
|
|
31
|
+
compatible: matchingUrls.length > 0,
|
|
32
|
+
agentUrls,
|
|
33
|
+
credentialUrls,
|
|
34
|
+
matchingUrls,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function uniqueStrings(values) {
|
|
39
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
|
|
40
|
+
}
|
|
@@ -3,6 +3,45 @@ const { useState: useVaultState, useEffect: useVaultEffect, useRef: useVaultRef
|
|
|
3
3
|
const DEMO_VAULTS = [{ id: 'vlt_demo', display_name: 'Demo integrations', created_at: '2026-07-01T00:00:00Z', archived_at: null }];
|
|
4
4
|
const DEMO_CREDENTIALS = [{ id: 'vcrd_demo', display_name: 'Demo MCP OAuth', archived_at: null, auth: { type: 'mcp_oauth', mcp_server_url: 'https://mcp.example.test/mcp', expires_at: '2026-12-01T00:00:00Z', refresh: { token_endpoint: 'https://auth.example.test/token', scope: 'read', token_endpoint_auth: { type: 'none' } } } }];
|
|
5
5
|
|
|
6
|
+
async function runMcpOauthPopup(startFlow, api = window.OmaConsoleApi) {
|
|
7
|
+
const popup = window.open('', 'oma-mcp-oauth', 'popup,width=720,height=760');
|
|
8
|
+
if (!popup) throw new Error('The authorization window was blocked. Allow popups for this console and try again.');
|
|
9
|
+
let flowId = null;
|
|
10
|
+
let wakePoll = null;
|
|
11
|
+
const onMessage = (event) => {
|
|
12
|
+
if (event.origin !== window.location.origin || event.data?.type !== 'oma:mcp-oauth') return;
|
|
13
|
+
if (flowId !== null && event.data.flowId !== flowId) return;
|
|
14
|
+
wakePoll?.();
|
|
15
|
+
};
|
|
16
|
+
window.addEventListener('message', onMessage);
|
|
17
|
+
try {
|
|
18
|
+
const started = await startFlow();
|
|
19
|
+
flowId = started.flow_id;
|
|
20
|
+
popup.location.replace(started.authorization_url);
|
|
21
|
+
const expiresAt = Date.parse(started.expires_at);
|
|
22
|
+
while (Date.now() < expiresAt) {
|
|
23
|
+
const status = await api.getMcpOauthFlow(started.flow_id);
|
|
24
|
+
if (status.status === 'connected') { popup.close(); return status; }
|
|
25
|
+
if (status.status === 'failed' || status.status === 'expired') {
|
|
26
|
+
popup.close();
|
|
27
|
+
throw new Error(status.error?.message || 'The provider did not complete authorization.');
|
|
28
|
+
}
|
|
29
|
+
if (popup.closed) throw new Error('The authorization window was closed before the connection completed.');
|
|
30
|
+
await new Promise((resolve) => {
|
|
31
|
+
const timer = setTimeout(resolve, 750);
|
|
32
|
+
wakePoll = () => { clearTimeout(timer); resolve(); };
|
|
33
|
+
});
|
|
34
|
+
wakePoll = null;
|
|
35
|
+
}
|
|
36
|
+
throw new Error('The authorization window expired. Start Connect again.');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
popup.close();
|
|
39
|
+
throw error;
|
|
40
|
+
} finally {
|
|
41
|
+
window.removeEventListener('message', onMessage);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
6
45
|
function ToneBadge({ tone, children }) {
|
|
7
46
|
return <span className={'badge ' + VaultsData.toneBadgeClass(tone)}><i className="dot" />{children}</span>;
|
|
8
47
|
}
|
|
@@ -17,12 +56,14 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
17
56
|
const [confirm, setConfirm] = useVaultState(null);
|
|
18
57
|
const [validation, setValidation] = useVaultState(null);
|
|
19
58
|
const [validating, setValidating] = useVaultState(false);
|
|
59
|
+
const [oauthBusy, setOauthBusy] = useVaultState(null);
|
|
60
|
+
const [oauthNotice, setOauthNotice] = useVaultState(null);
|
|
20
61
|
const epoch = useVaultRef(0);
|
|
21
62
|
const selectedId = selected?.id || null;
|
|
22
63
|
|
|
23
64
|
const refresh = () => {
|
|
24
65
|
const current = ++epoch.current;
|
|
25
|
-
setError(null); setVaults(null); setSelected(null); setCredentials(null); setWarning(null);
|
|
66
|
+
setError(null); setVaults(null); setSelected(null); setCredentials(null); setWarning(null); setOauthNotice(null);
|
|
26
67
|
if (mode !== 'api') { setVaults(DEMO_VAULTS.map(VaultsData.vaultRow)); return; }
|
|
27
68
|
OmaConsoleApi.listVaults().then((page) => {
|
|
28
69
|
if (current !== epoch.current) return;
|
|
@@ -38,7 +79,7 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
38
79
|
selectedRef.current = selectedId;
|
|
39
80
|
const open = (vault) => {
|
|
40
81
|
const current = ++epoch.current;
|
|
41
|
-
selectedRef.current = vault.id; setSelected(vault); setCredentials(null); setDetailError(null); setValidation(null); setValidating(false); setWarning(null);
|
|
82
|
+
selectedRef.current = vault.id; setSelected(vault); setCredentials(null); setDetailError(null); setValidation(null); setValidating(false); setOauthBusy(null); setOauthNotice(null); setWarning(null);
|
|
42
83
|
if (mode !== 'api') { setCredentials(DEMO_CREDENTIALS.map(VaultsData.credentialRow)); return; }
|
|
43
84
|
OmaConsoleApi.listVaultCredentials(vault.id).then((page) => {
|
|
44
85
|
if (!VaultsData.isCurrentVaultResult(current, epoch.current, vault.id, selectedRef.current)) return;
|
|
@@ -46,7 +87,7 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
46
87
|
if (page.truncated) setWarning(VaultsData.truncationWarning('credentials'));
|
|
47
88
|
}).catch((err) => { if (VaultsData.isCurrentVaultResult(current, epoch.current, vault.id, selectedRef.current)) setDetailError(err); });
|
|
48
89
|
};
|
|
49
|
-
const back = () => { ++epoch.current; setSelected(null); setCredentials(null); setValidation(null); setValidating(false); setWarning(null); onBackToVaults(); };
|
|
90
|
+
const back = () => { ++epoch.current; setSelected(null); setCredentials(null); setValidation(null); setValidating(false); setOauthBusy(null); setOauthNotice(null); setWarning(null); onBackToVaults(); };
|
|
50
91
|
const validate = (credential) => {
|
|
51
92
|
// Guard the result the same way the list/detail fetches are guarded: a
|
|
52
93
|
// probe can take seconds, and the operator can navigate to another vault
|
|
@@ -61,6 +102,49 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
61
102
|
.catch((err) => { if (isCurrent()) setValidation({ error: err.message }); })
|
|
62
103
|
.finally(() => { if (isCurrent()) setValidating(false); });
|
|
63
104
|
};
|
|
105
|
+
const reauthorize = async (credential) => {
|
|
106
|
+
const startedEpoch = epoch.current;
|
|
107
|
+
const vaultId = selected.id;
|
|
108
|
+
const isCurrent = () => VaultsData.isCurrentVaultResult(startedEpoch, epoch.current, vaultId, selectedRef.current);
|
|
109
|
+
setOauthBusy(credential.id); setOauthNotice(null);
|
|
110
|
+
try {
|
|
111
|
+
if (mode === 'demo') {
|
|
112
|
+
setOauthNotice({ tone:'ok', message:'Demo OAuth connection renewed.' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await runMcpOauthPopup(
|
|
116
|
+
() => OmaConsoleApi.reauthorizeMcpOauthCredential(vaultId, credential.id),
|
|
117
|
+
);
|
|
118
|
+
const page = await OmaConsoleApi.listVaultCredentials(vaultId);
|
|
119
|
+
if (isCurrent()) {
|
|
120
|
+
setCredentials(page.data.map(VaultsData.credentialRow));
|
|
121
|
+
setOauthNotice({ tone:'ok', message:'OAuth connection renewed. Automatic refresh remains enabled.' });
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (isCurrent()) setOauthNotice({ tone:'error', message:err.message || 'Reauthorization failed.' });
|
|
125
|
+
} finally {
|
|
126
|
+
if (isCurrent()) setOauthBusy(null);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const disconnect = async (credential) => {
|
|
130
|
+
const startedEpoch = epoch.current;
|
|
131
|
+
const vaultId = selected.id;
|
|
132
|
+
const isCurrent = () => VaultsData.isCurrentVaultResult(startedEpoch, epoch.current, vaultId, selectedRef.current);
|
|
133
|
+
setConfirm(null); setOauthBusy(credential.id); setOauthNotice(null);
|
|
134
|
+
try {
|
|
135
|
+
if (mode === 'demo') {
|
|
136
|
+
if (isCurrent()) setCredentials((rows) => rows.map((row) => row.id === credential.id ? { ...row, archivedAt:new Date().toISOString() } : row));
|
|
137
|
+
} else {
|
|
138
|
+
const archived = VaultsData.credentialRow(await OmaConsoleApi.archiveVaultCredential(vaultId, credential.id));
|
|
139
|
+
if (isCurrent()) setCredentials((rows) => rows.map((row) => row.id === credential.id ? archived : row));
|
|
140
|
+
}
|
|
141
|
+
if (isCurrent()) setOauthNotice({ tone:'neutral', message:'Credential disconnected and archived. Existing sessions can no longer use it.' });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (isCurrent()) setOauthNotice({ tone:'error', message:err.message || 'Disconnect failed.' });
|
|
144
|
+
} finally {
|
|
145
|
+
if (isCurrent()) setOauthBusy(null);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
64
148
|
useVaultEffect(() => {
|
|
65
149
|
if (!initialVaultId || !vaults || selected) return;
|
|
66
150
|
const vault = vaults.find((item) => item.id === initialVaultId);
|
|
@@ -70,7 +154,9 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
70
154
|
const body = selected
|
|
71
155
|
? <VaultDetail vault={selected} credentials={credentials} error={detailError} warning={warning}
|
|
72
156
|
onBack={back} onRetry={() => open(selected)}
|
|
73
|
-
mode={mode} readOnly={readOnly} onCreateCredential={() => onCreateCredential(selected)} validating={validating} validation={validation}
|
|
157
|
+
mode={mode} readOnly={readOnly} onCreateCredential={() => onCreateCredential(selected)} validating={validating} validation={validation}
|
|
158
|
+
oauthBusy={oauthBusy} oauthNotice={oauthNotice} onValidate={(credential) => setConfirm({ kind:'validate', credential })}
|
|
159
|
+
onReauthorize={reauthorize} onDisconnect={(credential) => setConfirm({ kind:'disconnect', credential })} />
|
|
74
160
|
: <div className="main-scroll scroll fade-in">
|
|
75
161
|
<PageHead title="Vaults" sub="Manage workspace vaults and validate MCP OAuth credentials."
|
|
76
162
|
action="Create vault" onAction={onCreate} readOnly={readOnly} endpoint="POST /v1/vaults" />
|
|
@@ -92,14 +178,18 @@ function VaultsView({ mode, initialVaultId, onCreate, onCreateCredential, readOn
|
|
|
92
178
|
// made Validate a no-op once a vault was open).
|
|
93
179
|
return <>
|
|
94
180
|
{body}
|
|
95
|
-
{confirm && <ConfirmDialog icon="alert" title="Validate credential"
|
|
181
|
+
{confirm?.kind === 'validate' && <ConfirmDialog icon="alert" title="Validate credential"
|
|
96
182
|
message="Validate contacts the MCP server with this credential and may refresh the token at the provider."
|
|
97
183
|
confirmLabel="Validate" endpoint="POST /v1/vaults/:id/credentials/:id/mcp_oauth_validate"
|
|
98
|
-
onClose={() => setConfirm(null)} onConfirm={() => validate(confirm)} />}
|
|
184
|
+
onClose={() => setConfirm(null)} onConfirm={() => validate(confirm.credential)} />}
|
|
185
|
+
{confirm?.kind === 'disconnect' && <ConfirmDialog icon="archive" danger title="Disconnect credential"
|
|
186
|
+
message="Archive this credential? Existing sessions will no longer be able to use it. Provider-side authorization is not revoked."
|
|
187
|
+
confirmLabel="Disconnect" endpoint="POST /v1/vaults/:id/credentials/:id/archive"
|
|
188
|
+
onClose={() => setConfirm(null)} onConfirm={() => disconnect(confirm.credential)} />}
|
|
99
189
|
</>;
|
|
100
190
|
}
|
|
101
191
|
|
|
102
|
-
function VaultDetail({ vault, credentials, error, warning, onBack, onRetry, mode, readOnly, onCreateCredential, validating, validation, onValidate }) {
|
|
192
|
+
function VaultDetail({ vault, credentials, error, warning, onBack, onRetry, mode, readOnly, onCreateCredential, validating, validation, onValidate, oauthBusy, oauthNotice, onReauthorize, onDisconnect }) {
|
|
103
193
|
return <div className="main-scroll scroll fade-in"><PageHead title={vault.displayName} sub={vault.id} />
|
|
104
194
|
<div className="toolbar"><button className="btn" onClick={onBack}>Back to vaults</button>{!readOnly && <button className="btn btn-primary" onClick={onCreateCredential}><Icon name="plus" size={15} />Add credential</button>}</div>
|
|
105
195
|
{warning && <div className="inline-warn"><Icon name="alert" size={14} /><span>{warning}</span></div>}
|
|
@@ -111,8 +201,13 @@ function VaultDetail({ vault, credentials, error, warning, onBack, onRetry, mode
|
|
|
111
201
|
<div style={{ display:'flex', gap:12, alignItems:'center' }}><span className="mono" style={{ color:'var(--soft)' }}>{c.id}</span><b>{c.displayName}</b><span className="pill">{c.authType}</span>{c.archivedAt && <St k="archived" />}</div>
|
|
112
202
|
<div className="field-hint" style={{ marginTop:6 }}>{c.serverUrl || 'No server URL'} · expires {VaultsData.relativeTime(c.expiresAt)}</div>
|
|
113
203
|
{c.refresh && <div className="field-hint">token host {c.refresh.tokenEndpointHost} · {c.refresh.scope || 'no scope'} · {c.refresh.endpointAuth || 'no endpoint auth'}</div>}
|
|
114
|
-
{mode !== 'mock' && !c.archivedAt && c.authType === 'mcp_oauth' && <
|
|
204
|
+
{mode !== 'mock' && !c.archivedAt && c.authType === 'mcp_oauth' && <div style={{ display:'flex', gap:8, marginTop:8 }}>
|
|
205
|
+
<button className="btn" disabled={validating || oauthBusy === c.id} onClick={() => onValidate(c)}>{validating ? 'Validating…' : 'Validate'}</button>
|
|
206
|
+
{c.refresh && <button className="btn" disabled={oauthBusy === c.id} onClick={() => onReauthorize(c)}>{oauthBusy === c.id ? 'Connecting…' : 'Reauthorize'}</button>}
|
|
207
|
+
<button className="btn btn-danger" disabled={oauthBusy === c.id || readOnly} onClick={() => onDisconnect(c)}>Disconnect</button>
|
|
208
|
+
</div>}
|
|
115
209
|
</div>)}</div>}
|
|
210
|
+
{oauthNotice && <div className={oauthNotice.tone === 'error' ? 'inline-warn' : 'panel'} role="status" style={{ marginTop:12, padding:14 }}><span>{oauthNotice.message}</span></div>}
|
|
116
211
|
{validation && <ValidationResult value={validation} />}
|
|
117
212
|
</div>;
|
|
118
213
|
}
|
|
@@ -168,4 +263,4 @@ function CredentialHealthView({ workspaceId, onBack, onReauth }) {
|
|
|
168
263
|
</div>;
|
|
169
264
|
}
|
|
170
265
|
|
|
171
|
-
Object.assign(window, { VaultsView, CredentialHealthView, VaultsData: window.VaultsData || {} });
|
|
266
|
+
Object.assign(window, { VaultsView, CredentialHealthView, runMcpOauthPopup, VaultsData: window.VaultsData || {} });
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-managed-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Self-hostable implementation of Anthropic's Managed Agents API surface.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/oneryalcin/open-managed-agents.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/oneryalcin/open-managed-agents#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/oneryalcin/open-managed-agents/issues"
|
|
12
|
+
},
|
|
5
13
|
"type": "module",
|
|
6
14
|
"bin": {
|
|
7
15
|
"oma": "bin/oma.mjs"
|
|
@@ -30,15 +38,15 @@
|
|
|
30
38
|
},
|
|
31
39
|
"dependencies": {
|
|
32
40
|
"@clack/prompts": "1.7.0",
|
|
33
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
34
|
-
"@hono/node-server": "2.
|
|
35
|
-
"@modelcontextprotocol/sdk": "1.
|
|
36
|
-
"hono": "4.
|
|
41
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
42
|
+
"@hono/node-server": "2.1.1",
|
|
43
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
44
|
+
"hono": "4.13.7",
|
|
37
45
|
"node-forge": "1.4.0",
|
|
38
46
|
"picocolors": "1.1.1",
|
|
39
47
|
"proper-lockfile": "4.1.2",
|
|
40
48
|
"typebox": "1.1.29",
|
|
41
|
-
"undici": "7.
|
|
49
|
+
"undici": "7.29.1",
|
|
42
50
|
"yauzl": "3.4.0"
|
|
43
51
|
},
|
|
44
52
|
"devDependencies": {
|
|
@@ -51,6 +59,6 @@
|
|
|
51
59
|
"playwright-core": "1.61.1",
|
|
52
60
|
"tsx": "4.21.0",
|
|
53
61
|
"typescript": "6.0.3",
|
|
54
|
-
"vitest": "4.1.
|
|
62
|
+
"vitest": "4.1.11"
|
|
55
63
|
}
|
|
56
64
|
}
|