create-loopengine 0.1.48 → 0.1.49
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 +45 -6
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -94,6 +94,10 @@ import {
|
|
|
94
94
|
listDeclaredEnvVars,
|
|
95
95
|
setEnvVar,
|
|
96
96
|
unsetEnvVar,
|
|
97
|
+
setAgentEnvVar,
|
|
98
|
+
unsetAgentEnvVar,
|
|
99
|
+
createAgentEnv,
|
|
100
|
+
agentDirFor,
|
|
97
101
|
EnvVarNameError,
|
|
98
102
|
readActauthConfig,
|
|
99
103
|
addActauthRule,
|
|
@@ -1024,6 +1028,14 @@ function handleEnvGet(res: ServerResponse, agentName: string): void {
|
|
|
1024
1028
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(listDeclaredEnvVars(agentName)))
|
|
1025
1029
|
}
|
|
1026
1030
|
|
|
1031
|
+
// `?slot=agent` targets this agent's own agents/<name>/.env (a per-agent
|
|
1032
|
+
// value, read by its tools through ToolContext.env) instead of the
|
|
1033
|
+
// project-wide .env — see loopengine's DeclaredEnvVar.slot.
|
|
1034
|
+
function envSlotOf(req: IncomingMessage): 'shared' | 'agent' | undefined {
|
|
1035
|
+
const slot = new URL(req.url ?? '/', 'http://localhost').searchParams.get('slot') ?? 'shared'
|
|
1036
|
+
return slot === 'shared' || slot === 'agent' ? slot : undefined
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1027
1039
|
// Writes a raw secret value into .env — meaningfully more sensitive than
|
|
1028
1040
|
// the rest of the admin surface (config/business data vs. an actual
|
|
1029
1041
|
// credential), so unlike every other route here, this one refuses
|
|
@@ -1043,16 +1055,31 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
|
|
|
1043
1055
|
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
1044
1056
|
return
|
|
1045
1057
|
}
|
|
1058
|
+
const slot = envSlotOf(req)
|
|
1059
|
+
if (!slot) {
|
|
1060
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: "slot must be 'shared' or 'agent'" }))
|
|
1061
|
+
return
|
|
1062
|
+
}
|
|
1046
1063
|
const body = await readJsonBody(req)
|
|
1047
1064
|
if (typeof body.value !== 'string') {
|
|
1048
1065
|
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'value is required' }))
|
|
1049
1066
|
return
|
|
1050
1067
|
}
|
|
1068
|
+
const declaredRows = listDeclaredEnvVars(agentName).filter((v) => v.name === varName)
|
|
1069
|
+
// A shared value for an agent-scoped var would never be read
|
|
1070
|
+
// (loopengine's createAgentEnv never falls back for one) — refuse it
|
|
1071
|
+
// rather than accept a write that silently does nothing.
|
|
1072
|
+
if (slot === 'shared' && declaredRows.some((v) => v.scope === 'agent')) {
|
|
1073
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(
|
|
1074
|
+
JSON.stringify({ error: `${varName} is declared scope 'agent' — set it per agent (slot=agent), not project-wide.` }),
|
|
1075
|
+
)
|
|
1076
|
+
return
|
|
1077
|
+
}
|
|
1051
1078
|
// A dropdown-backed var (AbilityEnvDecl.options) is still writable
|
|
1052
1079
|
// through this same raw route — worth rejecting an out-of-set value
|
|
1053
1080
|
// here too, not just skipping validation because the Admin UI's own
|
|
1054
1081
|
// <select> would never have produced one.
|
|
1055
|
-
const declared =
|
|
1082
|
+
const declared = declaredRows.find((v) => v.slot === slot) ?? declaredRows[0]
|
|
1056
1083
|
if (declared?.options && !declared.options.includes(body.value)) {
|
|
1057
1084
|
res.writeHead(400, { 'content-type': 'application/json' }).end(
|
|
1058
1085
|
JSON.stringify({ error: `"${body.value}" isn't one of this var's allowed values: ${declared.options.join(', ')}` }),
|
|
@@ -1060,7 +1087,8 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
|
|
|
1060
1087
|
return
|
|
1061
1088
|
}
|
|
1062
1089
|
try {
|
|
1063
|
-
|
|
1090
|
+
if (slot === 'agent') setAgentEnvVar(agentName, varName, body.value)
|
|
1091
|
+
else setEnvVar(varName, body.value)
|
|
1064
1092
|
} catch (err) {
|
|
1065
1093
|
const status = err instanceof EnvVarNameError ? 400 : 500
|
|
1066
1094
|
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
@@ -1073,7 +1101,7 @@ async function handleEnvPut(req: IncomingMessage, res: ServerResponse, agentName
|
|
|
1073
1101
|
// removing a secret from .env is still a secrets-management action,
|
|
1074
1102
|
// not just config, so this refuses outright rather than proceeding
|
|
1075
1103
|
// open when LOOPENGINE_ADMIN_AUTH isn't configured at all.
|
|
1076
|
-
async function handleEnvDelete(res: ServerResponse, agentName: string, varName: string): Promise<void> {
|
|
1104
|
+
async function handleEnvDelete(req: IncomingMessage, res: ServerResponse, agentName: string, varName: string): Promise<void> {
|
|
1077
1105
|
if (!adminAuth) {
|
|
1078
1106
|
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
1079
1107
|
JSON.stringify({ error: 'Refusing to unset an env var without LOOPENGINE_ADMIN_AUTH configured — set it before managing secrets through this route.' }),
|
|
@@ -1084,8 +1112,14 @@ async function handleEnvDelete(res: ServerResponse, agentName: string, varName:
|
|
|
1084
1112
|
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
1085
1113
|
return
|
|
1086
1114
|
}
|
|
1115
|
+
const slot = envSlotOf(req)
|
|
1116
|
+
if (!slot) {
|
|
1117
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: "slot must be 'shared' or 'agent'" }))
|
|
1118
|
+
return
|
|
1119
|
+
}
|
|
1087
1120
|
try {
|
|
1088
|
-
|
|
1121
|
+
if (slot === 'agent') unsetAgentEnvVar(agentName, varName)
|
|
1122
|
+
else unsetEnvVar(varName)
|
|
1089
1123
|
} catch (err) {
|
|
1090
1124
|
const status = err instanceof EnvVarNameError ? 400 : 500
|
|
1091
1125
|
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
@@ -1927,7 +1961,12 @@ async function handlePendingApprovalResolve(req: IncomingMessage, res: ServerRes
|
|
|
1927
1961
|
return { checkpoint, result: { kind: 'validation-error', error: validationError } }
|
|
1928
1962
|
}
|
|
1929
1963
|
try {
|
|
1930
|
-
const output = await tool.execute(editedArgs ?? item.args
|
|
1964
|
+
const output = await tool.execute(editedArgs ?? item.args, {
|
|
1965
|
+
agentName: entry.config.name,
|
|
1966
|
+
tenant: checkpoint.tenant,
|
|
1967
|
+
sessionId: checkpoint.sessionId,
|
|
1968
|
+
env: createAgentEnv(entry.config.name, agentDirFor(entry.config)),
|
|
1969
|
+
})
|
|
1931
1970
|
resultBlock = { type: 'tool_result', tool_use_id: item.toolUseId, content: JSON.stringify(output), is_error: false }
|
|
1932
1971
|
} catch (err) {
|
|
1933
1972
|
resultBlock = { type: 'tool_result', tool_use_id: item.toolUseId, content: `ERROR: ${err}`, is_error: true }
|
|
@@ -2626,7 +2665,7 @@ const server = createServer(async (req, res) => {
|
|
|
2626
2665
|
return
|
|
2627
2666
|
}
|
|
2628
2667
|
if (envVarMatch && req.method === 'DELETE') {
|
|
2629
|
-
await handleEnvDelete(res, decodeURIComponent(envVarMatch[1]), decodeURIComponent(envVarMatch[2]))
|
|
2668
|
+
await handleEnvDelete(req, res, decodeURIComponent(envVarMatch[1]), decodeURIComponent(envVarMatch[2]))
|
|
2630
2669
|
return
|
|
2631
2670
|
}
|
|
2632
2671
|
|