create-loopengine 0.1.73 → 0.1.75
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 +68 -1
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -82,10 +82,16 @@ import {
|
|
|
82
82
|
interviewTools,
|
|
83
83
|
listInterviews,
|
|
84
84
|
pickInterviewModel,
|
|
85
|
+
pickAgentModel,
|
|
85
86
|
previewCandidate,
|
|
86
87
|
startInterview,
|
|
87
88
|
setInterviewProject,
|
|
88
89
|
type InterviewAgentSource,
|
|
90
|
+
templatesPageHtml,
|
|
91
|
+
TemplateError,
|
|
92
|
+
createFromTemplate,
|
|
93
|
+
listTemplates,
|
|
94
|
+
previewTemplate,
|
|
89
95
|
globalConfigPageHtml,
|
|
90
96
|
addGatewayTool,
|
|
91
97
|
agentDir,
|
|
@@ -958,6 +964,58 @@ async function handleInterviews(req: IncomingMessage, res: ServerResponse, pathn
|
|
|
958
964
|
}
|
|
959
965
|
}
|
|
960
966
|
|
|
967
|
+
// Templates (loopengine's web/templates-admin.ts): ready-made agents,
|
|
968
|
+
// created through the same scaffold and registration as Agents → + New.
|
|
969
|
+
// A template agent takes the model of an agent that already works here,
|
|
970
|
+
// as an interview's does; with none, it starts on the scaffold's default.
|
|
971
|
+
async function handleStarterTemplates(req: IncomingMessage, res: ServerResponse, pathname: string): Promise<void> {
|
|
972
|
+
const json = (status: number, body: unknown): void => {
|
|
973
|
+
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' }).end(JSON.stringify(body))
|
|
974
|
+
}
|
|
975
|
+
const [id, action] = pathname.split('/').slice(2).map(decodeURIComponent) // after /starter-templates
|
|
976
|
+
const dirOf = (name: string) => join(projectDir(), 'agents', name)
|
|
977
|
+
const exists = (name: string) => Boolean(getEntry(name)) || existsSync(dirOf(name))
|
|
978
|
+
const envValue = (name: string, variable: string) => createAgentEnv(name, dirOf(name)).get(variable)
|
|
979
|
+
try {
|
|
980
|
+
if (!id) {
|
|
981
|
+
if (req.method === 'GET') return json(200, { templates: listTemplates(), canInstallAbilities: secretsProtected })
|
|
982
|
+
return json(405, { error: 'method not allowed' })
|
|
983
|
+
}
|
|
984
|
+
if (action === 'preview' && req.method === 'GET') return json(200, previewTemplate(id, { agentExists: exists, envValue }))
|
|
985
|
+
if (action === 'create' && req.method === 'POST') {
|
|
986
|
+
const model = pickAgentModel(interviewAgents())
|
|
987
|
+
const created = await createFromTemplate(id, {
|
|
988
|
+
agentExists: exists,
|
|
989
|
+
scaffoldAgent: async (name, systemPrompt) => {
|
|
990
|
+
const parsed = parseAgentTemplateOptions({ systemPrompt, ...(model ? { model } : {}) })
|
|
991
|
+
if (!parsed.ok) throw new TemplateError(parsed.error)
|
|
992
|
+
await scaffoldAgent(projectDir(), name, parsed.value)
|
|
993
|
+
},
|
|
994
|
+
registerAgent: async (name) => {
|
|
995
|
+
const indexPath = join(dirOf(name), 'index.ts')
|
|
996
|
+
registerAgent(await loadAgentModule(indexPath, indexPath))
|
|
997
|
+
},
|
|
998
|
+
// Same rule as the Abilities tab: installing runs third-party code,
|
|
999
|
+
// so only behind the admin login. The agent isn't loaded yet, so
|
|
1000
|
+
// nothing needs a restart.
|
|
1001
|
+
...(secretsProtected ? { installAbility: async (name: string, spec: string) => void (await installAbility(name, spec)) } : {}),
|
|
1002
|
+
envValue,
|
|
1003
|
+
messagingConfigured: (name) => {
|
|
1004
|
+
const entry = getEntry(name)
|
|
1005
|
+
return entry ? describeAgentNotifier(entry.config, defaultWebhook).source !== 'none' : false
|
|
1006
|
+
},
|
|
1007
|
+
})
|
|
1008
|
+
return json(200, created)
|
|
1009
|
+
}
|
|
1010
|
+
return json(404, { error: 'not found' })
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
1013
|
+
if (err instanceof TemplateError) return json(message === 'No such template' ? 404 : 400, { error: message })
|
|
1014
|
+
console.error('[loopengine] template failed:', err)
|
|
1015
|
+
return json(500, { error: `Couldn't create it: ${message}` })
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
961
1019
|
async function handleCreateAgent(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
962
1020
|
const body = await readJsonBody(req)
|
|
963
1021
|
if (typeof body.name !== 'string' || !body.name) {
|
|
@@ -1246,7 +1304,7 @@ async function handleSchedulesPost(req: IncomingMessage, res: ServerResponse, ag
|
|
|
1246
1304
|
const task = await createScheduledTask({
|
|
1247
1305
|
agent: agentName,
|
|
1248
1306
|
message: String(body.message ?? ''),
|
|
1249
|
-
schedule: (body.schedule ?? {}) as { run_at?: unknown; every?: unknown },
|
|
1307
|
+
schedule: (body.schedule ?? {}) as { run_at?: unknown; every?: unknown; start_at?: unknown },
|
|
1250
1308
|
task_name: typeof body.task_name === 'string' ? body.task_name : undefined,
|
|
1251
1309
|
notify: body.notify === true,
|
|
1252
1310
|
})
|
|
@@ -3272,6 +3330,15 @@ const server = createServer(async (req, res) => {
|
|
|
3272
3330
|
await handleInterviews(req, res, pathname)
|
|
3273
3331
|
return
|
|
3274
3332
|
}
|
|
3333
|
+
// Templates: the page, and its routes.
|
|
3334
|
+
if (req.method === 'GET' && pathname === '/templates') {
|
|
3335
|
+
res.writeHead(200, { 'content-type': 'text/html' }).end(templatesPageHtml)
|
|
3336
|
+
return
|
|
3337
|
+
}
|
|
3338
|
+
if (pathname === '/starter-templates' || pathname.startsWith('/starter-templates/')) {
|
|
3339
|
+
await handleStarterTemplates(req, res, pathname)
|
|
3340
|
+
return
|
|
3341
|
+
}
|
|
3275
3342
|
const editAgentMatch = req.method === 'PUT' && pathname.match(/^\/agents\/([^/]+)$/)
|
|
3276
3343
|
if (editAgentMatch) {
|
|
3277
3344
|
await handleEditAgent(req, res, decodeURIComponent(editAgentMatch[1]))
|