create-loopengine 0.1.74 → 0.1.76
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 +49 -7
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -197,6 +197,10 @@ import {
|
|
|
197
197
|
setModelProviderKey,
|
|
198
198
|
clearModelProviderKey,
|
|
199
199
|
ModelKeyError,
|
|
200
|
+
describeTimeZone,
|
|
201
|
+
setWorkspaceTimeZone,
|
|
202
|
+
TimeZoneError,
|
|
203
|
+
workspaceTimeZone,
|
|
200
204
|
describeGateways,
|
|
201
205
|
scaffoldAgent,
|
|
202
206
|
AgentNameError,
|
|
@@ -973,23 +977,37 @@ async function handleStarterTemplates(req: IncomingMessage, res: ServerResponse,
|
|
|
973
977
|
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' }).end(JSON.stringify(body))
|
|
974
978
|
}
|
|
975
979
|
const [id, action] = pathname.split('/').slice(2).map(decodeURIComponent) // after /starter-templates
|
|
976
|
-
const
|
|
980
|
+
const dirOf = (name: string) => join(projectDir(), 'agents', name)
|
|
981
|
+
const exists = (name: string) => Boolean(getEntry(name)) || existsSync(dirOf(name))
|
|
982
|
+
const envValue = (name: string, variable: string) => createAgentEnv(name, dirOf(name)).get(variable)
|
|
977
983
|
try {
|
|
978
984
|
if (!id) {
|
|
979
|
-
if (req.method === 'GET') return json(200, { templates: listTemplates() })
|
|
985
|
+
if (req.method === 'GET') return json(200, { templates: listTemplates(), canInstallAbilities: secretsProtected })
|
|
980
986
|
return json(405, { error: 'method not allowed' })
|
|
981
987
|
}
|
|
982
|
-
if (action === 'preview' && req.method === 'GET') return json(200, previewTemplate(id, { agentExists: exists }))
|
|
988
|
+
if (action === 'preview' && req.method === 'GET') return json(200, previewTemplate(id, { agentExists: exists, envValue }))
|
|
983
989
|
if (action === 'create' && req.method === 'POST') {
|
|
984
990
|
const model = pickAgentModel(interviewAgents())
|
|
985
991
|
const created = await createFromTemplate(id, {
|
|
986
992
|
agentExists: exists,
|
|
987
|
-
|
|
993
|
+
scaffoldAgent: async (name, systemPrompt) => {
|
|
988
994
|
const parsed = parseAgentTemplateOptions({ systemPrompt, ...(model ? { model } : {}) })
|
|
989
995
|
if (!parsed.ok) throw new TemplateError(parsed.error)
|
|
990
|
-
|
|
996
|
+
await scaffoldAgent(projectDir(), name, parsed.value)
|
|
997
|
+
},
|
|
998
|
+
registerAgent: async (name) => {
|
|
999
|
+
const indexPath = join(dirOf(name), 'index.ts')
|
|
991
1000
|
registerAgent(await loadAgentModule(indexPath, indexPath))
|
|
992
1001
|
},
|
|
1002
|
+
// Same rule as the Abilities tab: installing runs third-party code,
|
|
1003
|
+
// so only behind the admin login. The agent isn't loaded yet, so
|
|
1004
|
+
// nothing needs a restart.
|
|
1005
|
+
...(secretsProtected ? { installAbility: async (name: string, spec: string) => void (await installAbility(name, spec)) } : {}),
|
|
1006
|
+
envValue,
|
|
1007
|
+
messagingConfigured: (name) => {
|
|
1008
|
+
const entry = getEntry(name)
|
|
1009
|
+
return entry ? describeAgentNotifier(entry.config, defaultWebhook).source !== 'none' : false
|
|
1010
|
+
},
|
|
993
1011
|
})
|
|
994
1012
|
return json(200, created)
|
|
995
1013
|
}
|
|
@@ -1280,7 +1298,7 @@ function scheduleJson(res: ServerResponse, status: number, body: unknown): void
|
|
|
1280
1298
|
|
|
1281
1299
|
async function handleSchedulesGet(res: ServerResponse, agentName: string): Promise<void> {
|
|
1282
1300
|
if (!getEntry(agentName)) return scheduleJson(res, 404, { error: `unknown agent '${agentName}'` })
|
|
1283
|
-
scheduleJson(res, 200, { tasks: await listScheduledTasks({ agent: agentName }) })
|
|
1301
|
+
scheduleJson(res, 200, { tasks: await listScheduledTasks({ agent: agentName }), time_zone: workspaceTimeZone() })
|
|
1284
1302
|
}
|
|
1285
1303
|
|
|
1286
1304
|
async function handleSchedulesPost(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
|
|
@@ -1290,7 +1308,7 @@ async function handleSchedulesPost(req: IncomingMessage, res: ServerResponse, ag
|
|
|
1290
1308
|
const task = await createScheduledTask({
|
|
1291
1309
|
agent: agentName,
|
|
1292
1310
|
message: String(body.message ?? ''),
|
|
1293
|
-
schedule: (body.schedule ?? {}) as { run_at?: unknown; every?: unknown },
|
|
1311
|
+
schedule: (body.schedule ?? {}) as { run_at?: unknown; every?: unknown; start_at?: unknown; at?: unknown; weekday?: unknown },
|
|
1294
1312
|
task_name: typeof body.task_name === 'string' ? body.task_name : undefined,
|
|
1295
1313
|
notify: body.notify === true,
|
|
1296
1314
|
})
|
|
@@ -3360,6 +3378,7 @@ const server = createServer(async (req, res) => {
|
|
|
3360
3378
|
environment: process.env.LOOPENGINE_ENV ?? 'production',
|
|
3361
3379
|
publicUrl: process.env.LOOPENGINE_PUBLIC_URL ?? null,
|
|
3362
3380
|
scheduler: process.env.LOOPENGINE_SCHEDULER === 'off' ? 'off' : 'on',
|
|
3381
|
+
timeZone: workspaceTimeZone(),
|
|
3363
3382
|
}),
|
|
3364
3383
|
)
|
|
3365
3384
|
return
|
|
@@ -3419,6 +3438,29 @@ const server = createServer(async (req, res) => {
|
|
|
3419
3438
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
3420
3439
|
return
|
|
3421
3440
|
}
|
|
3441
|
+
// Settings → Time zone (web/global-config.ts): what "9:00" means in
|
|
3442
|
+
// schedules. Saved like a model key, so refused without auth too.
|
|
3443
|
+
if (req.method === 'GET' && pathname === '/config/time-zone') {
|
|
3444
|
+
res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' }).end(JSON.stringify({ ...describeTimeZone(), canSave: secretsProtected }))
|
|
3445
|
+
return
|
|
3446
|
+
}
|
|
3447
|
+
if (req.method === 'PUT' && pathname === '/config/time-zone') {
|
|
3448
|
+
if (!secretsProtected) {
|
|
3449
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
3450
|
+
JSON.stringify({ error: 'Refusing to change settings without LOOPENGINE_ADMIN_AUTH configured: set it first, or put LOOPENGINE_TIME_ZONE in .env.' }),
|
|
3451
|
+
)
|
|
3452
|
+
return
|
|
3453
|
+
}
|
|
3454
|
+
try {
|
|
3455
|
+
const body = await readJsonBody(req)
|
|
3456
|
+
const outcome = await setWorkspaceTimeZone(body.timeZone, { moveSchedules: body.moveSchedules === true })
|
|
3457
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true, ...outcome }))
|
|
3458
|
+
} catch (err) {
|
|
3459
|
+
const status = err instanceof TimeZoneError ? 400 : 500
|
|
3460
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
3461
|
+
}
|
|
3462
|
+
return
|
|
3463
|
+
}
|
|
3422
3464
|
if (req.method === 'GET' && pathname === '/config/gateways') {
|
|
3423
3465
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(await describeGateways()))
|
|
3424
3466
|
return
|