create-loopengine 0.1.31 → 0.1.33
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/package.json +1 -1
- package/template/adapters/http.ts +102 -3
package/package.json
CHANGED
|
@@ -95,6 +95,13 @@ import {
|
|
|
95
95
|
setDefaultDecision,
|
|
96
96
|
ActauthRuleExistsError,
|
|
97
97
|
ActauthRuleNotFoundError,
|
|
98
|
+
installAbility,
|
|
99
|
+
listInstalledAbilities,
|
|
100
|
+
AbilityAlreadyInstalledError,
|
|
101
|
+
AbilityCollisionError,
|
|
102
|
+
AbilityManifestError,
|
|
103
|
+
AbilityVersionError,
|
|
104
|
+
searchPublicAbilities,
|
|
98
105
|
describeModelProviders,
|
|
99
106
|
describeGateways,
|
|
100
107
|
scaffoldAgent,
|
|
@@ -920,8 +927,8 @@ function handleSkillDelete(res: ServerResponse, agentName: string, skillId: stri
|
|
|
920
927
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
921
928
|
}
|
|
922
929
|
|
|
923
|
-
// Backs the Admin UI's "Environment" section — every env var
|
|
924
|
-
// (see loopengine's own
|
|
930
|
+
// Backs the Admin UI's "Environment" section — every env var an ability
|
|
931
|
+
// (see loopengine's own ABILITIES.md, bin/ability-manager.ts) declared it
|
|
925
932
|
// needs for this agent, with set/not-set status only. Read-only, so no
|
|
926
933
|
// extra auth gate beyond the normal Basic Auth middleware every admin
|
|
927
934
|
// route already has.
|
|
@@ -938,7 +945,7 @@ function handleEnvGet(res: ServerResponse, agentName: string): void {
|
|
|
938
945
|
// credential), so unlike every other route here, this one refuses
|
|
939
946
|
// outright when LOOPENGINE_ADMIN_AUTH isn't set at all, rather than just
|
|
940
947
|
// warning at startup and proceeding open like the rest of this file does
|
|
941
|
-
// (see loopengine's own
|
|
948
|
+
// (see loopengine's own ABILITIES.md, "Managing ability secrets in the
|
|
942
949
|
// Admin UI" — LOOPENGINE_ADMIN_AUTH being optional today is an
|
|
943
950
|
// acceptable gap for config, not for secrets).
|
|
944
951
|
async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName: string, varName: string): Promise<void> {
|
|
@@ -967,6 +974,75 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
|
|
|
967
974
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
968
975
|
}
|
|
969
976
|
|
|
977
|
+
// Backs the Admin UI's Abilities tab list — everything currently
|
|
978
|
+
// installed for this agent. Read-only, so no extra auth gate beyond the
|
|
979
|
+
// normal Basic Auth middleware every admin route already has.
|
|
980
|
+
function handleAbilitiesGet(res: ServerResponse, agentName: string): void {
|
|
981
|
+
if (!getEntry(agentName)) {
|
|
982
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ abilities: listInstalledAbilities(agentName) }))
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Backs the Abilities tab's search box — proxies to npm's own public
|
|
989
|
+
// registry search (loopengine's own searchPublicAbilities) so the
|
|
990
|
+
// browser never needs direct access to registry.npmjs.org, and the
|
|
991
|
+
// actual query text (the loopengine-ability keyword filter) stays a
|
|
992
|
+
// server-side detail rather than something the client has to know to
|
|
993
|
+
// construct.
|
|
994
|
+
async function handleAbilitiesSearchGet(res: ServerResponse, agentName: string, query: string | null): Promise<void> {
|
|
995
|
+
if (!getEntry(agentName)) {
|
|
996
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
997
|
+
return
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
const results = await searchPublicAbilities(query ?? undefined)
|
|
1001
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ results }))
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
res.writeHead(502, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Installs an ability — any spec `npm pack` accepts (a bare/scoped
|
|
1008
|
+
// registry name, a pinned version, a git URL, or a local `file:../path`
|
|
1009
|
+
// for a private ability) — into this agent's own tree. Meaningfully
|
|
1010
|
+
// more sensitive than the rest of this admin surface: this runs
|
|
1011
|
+
// arbitrary third-party code inside the agent's project the next time
|
|
1012
|
+
// it's loaded, not just a config value or a credential. Same posture as
|
|
1013
|
+
// handleEnvPut's own secret-writing gate above — refuses outright when
|
|
1014
|
+
// LOOPENGINE_ADMIN_AUTH isn't configured at all, rather than proceeding
|
|
1015
|
+
// open like most routes here do.
|
|
1016
|
+
async function handleAbilityInstallPost(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
|
|
1017
|
+
if (!adminAuth) {
|
|
1018
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
1019
|
+
JSON.stringify({ error: 'Refusing to install an ability without LOOPENGINE_ADMIN_AUTH configured — set it before installing abilities through this route.' }),
|
|
1020
|
+
)
|
|
1021
|
+
return
|
|
1022
|
+
}
|
|
1023
|
+
if (!getEntry(agentName)) {
|
|
1024
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
1025
|
+
return
|
|
1026
|
+
}
|
|
1027
|
+
const body = await readJsonBody(req)
|
|
1028
|
+
if (typeof body.spec !== 'string' || !body.spec) {
|
|
1029
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'spec is required' }))
|
|
1030
|
+
return
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const result = await installAbility(agentName, body.spec)
|
|
1034
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(result))
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
const status =
|
|
1037
|
+
err instanceof AbilityAlreadyInstalledError || err instanceof AbilityCollisionError
|
|
1038
|
+
? 409
|
|
1039
|
+
: err instanceof AbilityManifestError || err instanceof AbilityVersionError
|
|
1040
|
+
? 422
|
|
1041
|
+
: 500
|
|
1042
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
970
1046
|
// Backs the Actauth tab's rule editor — parses a rule body shared by both
|
|
971
1047
|
// the add (POST) and update (PUT, minus `name`) routes below.
|
|
972
1048
|
function parseActauthRuleBody(body: Record<string, unknown>, requireName: boolean): { ok: true; value: { name: string; scope: string; tool: string; decision: Decision } } | { ok: false; error: string } {
|
|
@@ -2116,6 +2192,29 @@ const server = createServer(async (req, res) => {
|
|
|
2116
2192
|
return
|
|
2117
2193
|
}
|
|
2118
2194
|
|
|
2195
|
+
// Backs the Admin UI's Abilities tab — see handleAbilitiesGet/
|
|
2196
|
+
// handleAbilitiesSearchGet/handleAbilityInstallPost's own doc
|
|
2197
|
+
// comments above. The /search route is checked before the plain
|
|
2198
|
+
// :agentName routes right below since 'search' would otherwise
|
|
2199
|
+
// itself need to be excluded from matching as an agent name (it
|
|
2200
|
+
// never would be one in practice, but this ordering means never
|
|
2201
|
+
// having to think about it).
|
|
2202
|
+
const abilitiesSearchMatch = pathname.match(/^\/agents\/([^/]+)\/abilities\/search$/)
|
|
2203
|
+
if (abilitiesSearchMatch && req.method === 'GET') {
|
|
2204
|
+
const query = new URL(req.url ?? '/', 'http://localhost').searchParams.get('q')
|
|
2205
|
+
await handleAbilitiesSearchGet(res, decodeURIComponent(abilitiesSearchMatch[1]), query)
|
|
2206
|
+
return
|
|
2207
|
+
}
|
|
2208
|
+
const abilitiesMatch = pathname.match(/^\/agents\/([^/]+)\/abilities$/)
|
|
2209
|
+
if (abilitiesMatch && req.method === 'GET') {
|
|
2210
|
+
handleAbilitiesGet(res, decodeURIComponent(abilitiesMatch[1]))
|
|
2211
|
+
return
|
|
2212
|
+
}
|
|
2213
|
+
if (abilitiesMatch && req.method === 'POST') {
|
|
2214
|
+
await handleAbilityInstallPost(req, res, decodeURIComponent(abilitiesMatch[1]))
|
|
2215
|
+
return
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2119
2218
|
// Backs the Actauth tab's rule editor and default_decision control.
|
|
2120
2219
|
// The default-decision route is checked before the :ruleName routes
|
|
2121
2220
|
// right below since 'default-decision' would otherwise itself match
|