create-loopengine 0.1.34 → 0.1.36

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-loopengine",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Scaffold a new loopengine agent project — a runnable HTTP server with one example agent, in your own repo.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -96,11 +96,13 @@ import {
96
96
  ActauthRuleExistsError,
97
97
  ActauthRuleNotFoundError,
98
98
  installAbility,
99
+ upgradeAbility,
99
100
  listInstalledAbilities,
100
101
  AbilityAlreadyInstalledError,
101
102
  AbilityCollisionError,
102
103
  AbilityManifestError,
103
104
  AbilityVersionError,
105
+ AbilityNotInstalledError,
104
106
  searchPublicAbilities,
105
107
  describeModelProviders,
106
108
  describeGateways,
@@ -975,6 +977,17 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
975
977
  res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'value is required' }))
976
978
  return
977
979
  }
980
+ // A dropdown-backed var (AbilityEnvDecl.options) is still writable
981
+ // through this same raw route — worth rejecting an out-of-set value
982
+ // here too, not just skipping validation because the Admin UI's own
983
+ // <select> would never have produced one.
984
+ const declared = listDeclaredEnvVars(agentName).find((v) => v.name === varName)
985
+ if (declared?.options && !declared.options.includes(body.value)) {
986
+ res.writeHead(400, { 'content-type': 'application/json' }).end(
987
+ JSON.stringify({ error: `"${body.value}" isn't one of this var's allowed values: ${declared.options.join(', ')}` }),
988
+ )
989
+ return
990
+ }
978
991
  try {
979
992
  setEnvVar(varName, body.value)
980
993
  } catch (err) {
@@ -1054,6 +1067,38 @@ async function handleAbilityInstallPost(req: IncomingMessage, res: ServerRespons
1054
1067
  }
1055
1068
  }
1056
1069
 
1070
+ // Upgrades an already-installed ability to whatever it currently
1071
+ // resolves to (or, optionally, a specific spec) — same admin-auth
1072
+ // posture as handleAbilityInstallPost above, since this runs arbitrary
1073
+ // third-party code inside the agent's project the next time it's
1074
+ // loaded, same as install does.
1075
+ async function handleAbilityUpgradePost(req: IncomingMessage, res: ServerResponse, agentName: string, abilityName: string): Promise<void> {
1076
+ if (!adminAuth) {
1077
+ res.writeHead(403, { 'content-type': 'application/json' }).end(
1078
+ JSON.stringify({ error: 'Refusing to upgrade an ability without LOOPENGINE_ADMIN_AUTH configured — set it before upgrading abilities through this route.' }),
1079
+ )
1080
+ return
1081
+ }
1082
+ if (!getEntry(agentName)) {
1083
+ res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
1084
+ return
1085
+ }
1086
+ const body = await readJsonBody(req)
1087
+ const spec = typeof body.spec === 'string' && body.spec ? body.spec : undefined
1088
+ try {
1089
+ const result = await upgradeAbility(agentName, abilityName, spec ? { spec } : {})
1090
+ res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(result))
1091
+ } catch (err) {
1092
+ const status =
1093
+ err instanceof AbilityNotInstalledError
1094
+ ? 404
1095
+ : err instanceof AbilityManifestError || err instanceof AbilityVersionError
1096
+ ? 422
1097
+ : 500
1098
+ res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
1099
+ }
1100
+ }
1101
+
1057
1102
  // Backs the Actauth tab's rule editor — parses a rule body shared by both
1058
1103
  // the add (POST) and update (PUT, minus `name`) routes below.
1059
1104
  function parseActauthRuleBody(body: Record<string, unknown>, requireName: boolean): { ok: true; value: { name: string; scope: string; tool: string; decision: Decision } } | { ok: false; error: string } {
@@ -2232,6 +2277,11 @@ const server = createServer(async (req, res) => {
2232
2277
  await handleAbilitiesSearchGet(res, decodeURIComponent(abilitiesSearchMatch[1]), query)
2233
2278
  return
2234
2279
  }
2280
+ const abilityUpgradeMatch = pathname.match(/^\/agents\/([^/]+)\/abilities\/([^/]+)\/upgrade$/)
2281
+ if (abilityUpgradeMatch && req.method === 'POST') {
2282
+ await handleAbilityUpgradePost(req, res, decodeURIComponent(abilityUpgradeMatch[1]), decodeURIComponent(abilityUpgradeMatch[2]))
2283
+ return
2284
+ }
2235
2285
  const abilitiesMatch = pathname.match(/^\/agents\/([^/]+)\/abilities$/)
2236
2286
  if (abilitiesMatch && req.method === 'GET') {
2237
2287
  handleAbilitiesGet(res, decodeURIComponent(abilitiesMatch[1]))