create-loopengine 0.1.46 → 0.1.48
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 +65 -0
package/package.json
CHANGED
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
SkillNotFoundError,
|
|
94
94
|
listDeclaredEnvVars,
|
|
95
95
|
setEnvVar,
|
|
96
|
+
unsetEnvVar,
|
|
96
97
|
EnvVarNameError,
|
|
97
98
|
readActauthConfig,
|
|
98
99
|
addActauthRule,
|
|
@@ -103,6 +104,7 @@ import {
|
|
|
103
104
|
ActauthRuleNotFoundError,
|
|
104
105
|
installAbility,
|
|
105
106
|
upgradeAbility,
|
|
107
|
+
removeAbility,
|
|
106
108
|
listInstalledAbilities,
|
|
107
109
|
backfillDependencies,
|
|
108
110
|
AbilityAlreadyInstalledError,
|
|
@@ -1067,6 +1069,31 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
|
|
|
1067
1069
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
1068
1070
|
}
|
|
1069
1071
|
|
|
1072
|
+
// Same auth requirement as handleEnvPut above and the same reasoning —
|
|
1073
|
+
// removing a secret from .env is still a secrets-management action,
|
|
1074
|
+
// not just config, so this refuses outright rather than proceeding
|
|
1075
|
+
// open when LOOPENGINE_ADMIN_AUTH isn't configured at all.
|
|
1076
|
+
async function handleEnvDelete(res: ServerResponse, agentName: string, varName: string): Promise<void> {
|
|
1077
|
+
if (!adminAuth) {
|
|
1078
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
1079
|
+
JSON.stringify({ error: 'Refusing to unset an env var without LOOPENGINE_ADMIN_AUTH configured — set it before managing secrets through this route.' }),
|
|
1080
|
+
)
|
|
1081
|
+
return
|
|
1082
|
+
}
|
|
1083
|
+
if (!getEntry(agentName)) {
|
|
1084
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
1087
|
+
try {
|
|
1088
|
+
unsetEnvVar(varName)
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
const status = err instanceof EnvVarNameError ? 400 : 500
|
|
1091
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
1092
|
+
return
|
|
1093
|
+
}
|
|
1094
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1070
1097
|
// A cheap, good-enough presence check — existsSync(node_modules/<dep>)
|
|
1071
1098
|
// — rather than an actual module resolution (require.resolve/
|
|
1072
1099
|
// import.meta.resolve): this only ever decides whether to show an
|
|
@@ -1202,6 +1229,34 @@ async function handleAbilityUpgradePost(req: IncomingMessage, res: ServerRespons
|
|
|
1202
1229
|
}
|
|
1203
1230
|
}
|
|
1204
1231
|
|
|
1232
|
+
// Uninstalls an ability — deletes its tool/skill files and actauth
|
|
1233
|
+
// rules (bin/ability-manager.ts's own removeAbility). Same admin-auth
|
|
1234
|
+
// posture as install/upgrade above: this mutates the agent's own tree,
|
|
1235
|
+
// even though — unlike install/upgrade — it never runs new third-party
|
|
1236
|
+
// code. `removed`/`refused` pass straight through on success (not an
|
|
1237
|
+
// error: a dirty file left in place, same as remove-ability's own CLI
|
|
1238
|
+
// behavior, is a normal partial outcome the Admin UI shows and offers
|
|
1239
|
+
// to retry with force, not a failure).
|
|
1240
|
+
async function handleAbilityUninstallDelete(res: ServerResponse, agentName: string, abilityName: string, force: boolean): Promise<void> {
|
|
1241
|
+
if (!adminAuth) {
|
|
1242
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
1243
|
+
JSON.stringify({ error: 'Refusing to uninstall an ability without LOOPENGINE_ADMIN_AUTH configured — set it before uninstalling abilities through this route.' }),
|
|
1244
|
+
)
|
|
1245
|
+
return
|
|
1246
|
+
}
|
|
1247
|
+
if (!getEntry(agentName)) {
|
|
1248
|
+
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
1249
|
+
return
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
const result = removeAbility(agentName, abilityName, force)
|
|
1253
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(result))
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
const status = err instanceof AbilityNotInstalledError ? 404 : 500
|
|
1256
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1205
1260
|
const execFileAsync = promisify(execFile)
|
|
1206
1261
|
const NPM_INSTALL_DEPS_TIMEOUT_MS = 180_000
|
|
1207
1262
|
|
|
@@ -2570,6 +2625,10 @@ const server = createServer(async (req, res) => {
|
|
|
2570
2625
|
await handleEnvPut(req, res, decodeURIComponent(envVarMatch[1]), decodeURIComponent(envVarMatch[2]))
|
|
2571
2626
|
return
|
|
2572
2627
|
}
|
|
2628
|
+
if (envVarMatch && req.method === 'DELETE') {
|
|
2629
|
+
await handleEnvDelete(res, decodeURIComponent(envVarMatch[1]), decodeURIComponent(envVarMatch[2]))
|
|
2630
|
+
return
|
|
2631
|
+
}
|
|
2573
2632
|
|
|
2574
2633
|
// Backs the Admin UI's Abilities tab — see handleAbilitiesGet/
|
|
2575
2634
|
// handleAbilitiesSearchGet/handleAbilityInstallPost's own doc
|
|
@@ -2594,6 +2653,12 @@ const server = createServer(async (req, res) => {
|
|
|
2594
2653
|
await handleAbilityInstallDepsPost(req, res, decodeURIComponent(abilityInstallDepsMatch[1]), decodeURIComponent(abilityInstallDepsMatch[2]))
|
|
2595
2654
|
return
|
|
2596
2655
|
}
|
|
2656
|
+
const abilityMatch = pathname.match(/^\/agents\/([^/]+)\/abilities\/([^/]+)$/)
|
|
2657
|
+
if (abilityMatch && req.method === 'DELETE') {
|
|
2658
|
+
const force = new URL(req.url ?? '/', 'http://localhost').searchParams.get('force') === 'true'
|
|
2659
|
+
await handleAbilityUninstallDelete(res, decodeURIComponent(abilityMatch[1]), decodeURIComponent(abilityMatch[2]), force)
|
|
2660
|
+
return
|
|
2661
|
+
}
|
|
2597
2662
|
const abilitiesMatch = pathname.match(/^\/agents\/([^/]+)\/abilities$/)
|
|
2598
2663
|
if (abilitiesMatch && req.method === 'GET') {
|
|
2599
2664
|
await handleAbilitiesGet(res, decodeURIComponent(abilitiesMatch[1]))
|