loopengine 0.1.92 → 0.1.94
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/README.md +14 -5
- package/dist/core/agent-config.d.ts +19 -7
- package/dist/core/email-senders.d.ts +26 -0
- package/dist/core/email-senders.js +108 -0
- package/dist/core/http-notifier.js +6 -6
- package/dist/core/http-notify-triggers/email.d.ts +14 -8
- package/dist/core/http-notify-triggers/email.js +70 -28
- package/dist/core/notifier-file.d.ts +11 -3
- package/dist/core/notifier-file.js +37 -5
- package/dist/core/scheduler.d.ts +22 -0
- package/dist/core/scheduler.js +111 -12
- package/dist/core/system-agents/templates.d.ts +43 -0
- package/dist/core/system-agents/templates.js +290 -48
- package/dist/core/system-tools/schedule.js +16 -6
- package/dist/core/time-zone.d.ts +40 -0
- package/dist/core/time-zone.js +138 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- package/dist/web/agents-config-page.js +49 -11
- package/dist/web/global-config-page.js +65 -0
- package/dist/web/global-config.d.ts +24 -0
- package/dist/web/global-config.js +30 -0
- package/dist/web/notifier-admin.js +11 -2
- package/dist/web/templates-admin.d.ts +82 -7
- package/dist/web/templates-admin.js +207 -16
- package/dist/web/templates-page.js +31 -6
- package/package.json +5 -1
|
@@ -1,6 +1,33 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type StarterTemplate, type TemplateSchedule } from '../core/system-agents/templates.js';
|
|
2
2
|
export declare class TemplateError extends Error {
|
|
3
3
|
}
|
|
4
|
+
export interface TemplateCreateDeps {
|
|
5
|
+
agentExists(name: string): boolean;
|
|
6
|
+
/** Writes agents/<name>/index.ts, as Agents → + New does, without loading it. */
|
|
7
|
+
scaffoldAgent(name: string, systemPrompt: string): Promise<void>;
|
|
8
|
+
/** Loads the finished agent into the running server. */
|
|
9
|
+
registerAgent(name: string): Promise<void>;
|
|
10
|
+
/** Installs an ability (an npm spec) into the agent. Absent where
|
|
11
|
+
* installing isn't allowed (no admin login): the abilities then head the
|
|
12
|
+
* checklist instead. */
|
|
13
|
+
installAbility?: (name: string, spec: string) => Promise<void>;
|
|
14
|
+
/** A setting as the agent sees it (its own, else the shared one). */
|
|
15
|
+
envValue(name: string, variable: string): string | undefined;
|
|
16
|
+
/** Whether approval requests from the agent reach anyone. */
|
|
17
|
+
messagingConfigured(name: string): boolean;
|
|
18
|
+
}
|
|
19
|
+
/** What a project's adapters/http.ts from before full template setups
|
|
20
|
+
* (create-loopengine 0.1.74) passes: one call that writes and loads the
|
|
21
|
+
* agent. Still accepted, so upgrading loopengine alone doesn't break the
|
|
22
|
+
* Templates page; it just can't install abilities before the agent loads. */
|
|
23
|
+
export interface LegacyTemplateCreateDeps {
|
|
24
|
+
agentExists(name: string): boolean;
|
|
25
|
+
createAgent(name: string, systemPrompt: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface TemplateChecklistItem {
|
|
28
|
+
label: string;
|
|
29
|
+
href?: string;
|
|
30
|
+
}
|
|
4
31
|
/** A template as the page's cards show it. */
|
|
5
32
|
export interface TemplateSummary {
|
|
6
33
|
id: string;
|
|
@@ -9,11 +36,59 @@ export interface TemplateSummary {
|
|
|
9
36
|
summary: string;
|
|
10
37
|
trigger: string;
|
|
11
38
|
asksFirst: string[];
|
|
12
|
-
|
|
13
|
-
schedule:
|
|
39
|
+
abilities: string[];
|
|
40
|
+
schedule: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface TemplatePreview {
|
|
43
|
+
name: string;
|
|
44
|
+
systemPrompt: string;
|
|
45
|
+
abilities: string[];
|
|
46
|
+
skills: string[];
|
|
47
|
+
rules: {
|
|
48
|
+
tool: string;
|
|
49
|
+
decision: string;
|
|
50
|
+
}[];
|
|
51
|
+
keys: {
|
|
52
|
+
names: string[];
|
|
53
|
+
set: boolean;
|
|
54
|
+
}[];
|
|
55
|
+
settings: Record<string, string>;
|
|
56
|
+
schedule: string | null;
|
|
57
|
+
needsApprovals: boolean;
|
|
58
|
+
notes: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface CreatedTemplateAgent {
|
|
61
|
+
name: string;
|
|
62
|
+
abilities: {
|
|
63
|
+
installed: string[];
|
|
64
|
+
failed: {
|
|
65
|
+
name: string;
|
|
66
|
+
error: string;
|
|
67
|
+
}[];
|
|
68
|
+
};
|
|
69
|
+
checklist: TemplateChecklistItem[];
|
|
70
|
+
exampleMessage?: string;
|
|
71
|
+
schedule?: {
|
|
72
|
+
description: string;
|
|
73
|
+
timeZone?: string;
|
|
74
|
+
next_run_at?: string;
|
|
75
|
+
};
|
|
76
|
+
/** Set when the schedule couldn't be created; the agent still was. */
|
|
77
|
+
scheduleError?: string;
|
|
78
|
+
/** Set when the agent was written but couldn't be loaded. */
|
|
79
|
+
loadError?: string;
|
|
14
80
|
}
|
|
81
|
+
/** A failed install, in a line the checklist can show: npm's own output
|
|
82
|
+
* runs to many lines of command and log paths. */
|
|
83
|
+
export declare function installFailureReason(err: unknown): string;
|
|
84
|
+
export declare function describeTemplateSchedule(schedule: TemplateSchedule): string;
|
|
15
85
|
export declare function listTemplates(): TemplateSummary[];
|
|
16
|
-
export declare function previewTemplate(id: string, deps: Pick<
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
86
|
+
export declare function previewTemplate(id: string, deps: Pick<TemplateCreateDeps, 'agentExists'> & Partial<Pick<TemplateCreateDeps, 'envValue'>>): TemplatePreview;
|
|
87
|
+
/** What's left once the agent exists, each linked to where it's done. */
|
|
88
|
+
export declare function templateChecklist(template: StarterTemplate, name: string, deps: Pick<TemplateCreateDeps, 'envValue' | 'messagingConfigured'>, failed?: {
|
|
89
|
+
name: string;
|
|
90
|
+
error: string;
|
|
91
|
+
}[]): TemplateChecklistItem[];
|
|
92
|
+
/** Creates a new agent from the template, with its whole setup, under the
|
|
93
|
+
* first free name, so the same template can be used more than once. */
|
|
94
|
+
export declare function createFromTemplate(id: string, deps: TemplateCreateDeps | LegacyTemplateCreateDeps): Promise<CreatedTemplateAgent>;
|
|
@@ -1,37 +1,228 @@
|
|
|
1
1
|
// Backs the console's Templates page (/templates): the ready-made agents
|
|
2
|
-
// in core/system-agents/templates.ts, previewed and created
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
// in core/system-agents/templates.ts, previewed and created with their
|
|
3
|
+
// whole setup in one go — instructions, permission rules, abilities,
|
|
4
|
+
// skills, settings and a paused schedule — before the agent first loads,
|
|
5
|
+
// so nothing needs a restart. What only the person can do (their keys,
|
|
6
|
+
// where approvals go, turning the schedule on) becomes the checklist.
|
|
7
|
+
//
|
|
8
|
+
// Every agent made from a template asks before anything not covered by a
|
|
9
|
+
// rule. Its template's rules are written before its abilities' own, so
|
|
10
|
+
// they take precedence where both name the same tool.
|
|
11
|
+
import { createScheduledTask, pauseScheduledTask } from '../core/scheduler.js';
|
|
12
|
+
import { workspaceTimeZone } from '../core/time-zone.js';
|
|
13
|
+
import { STARTER_TEMPLATES, starterTemplate, templateScheduleStart } from '../core/system-agents/templates.js';
|
|
14
|
+
import { ActauthRuleExistsError, addActauthRule, setDefaultDecision } from './actauth-admin.js';
|
|
15
|
+
import { setAgentEnvVar } from './env-admin.js';
|
|
16
|
+
import { freeAgentName } from './interview-admin.js';
|
|
17
|
+
import { writeSkill } from './skills-admin.js';
|
|
10
18
|
export class TemplateError extends Error {
|
|
11
19
|
}
|
|
20
|
+
/** A failed install, in a line the checklist can show: npm's own output
|
|
21
|
+
* runs to many lines of command and log paths. */
|
|
22
|
+
export function installFailureReason(err) {
|
|
23
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
24
|
+
if (/E404|404 Not Found|is not in this registry/i.test(text))
|
|
25
|
+
return "it isn't published on npm yet";
|
|
26
|
+
if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN/.test(text))
|
|
27
|
+
return 'npm could not be reached; try again from the Abilities tab';
|
|
28
|
+
const line = text.split('\n').map((l) => l.trim()).find((l) => l && !/^Command failed:/.test(l)) ?? text.split('\n')[0];
|
|
29
|
+
return line.length > 200 ? `${line.slice(0, 200)}…` : line;
|
|
30
|
+
}
|
|
12
31
|
function templateAt(id) {
|
|
13
32
|
const template = starterTemplate(id);
|
|
14
33
|
if (!template)
|
|
15
34
|
throw new TemplateError('No such template');
|
|
16
35
|
return template;
|
|
17
36
|
}
|
|
37
|
+
const UNITS = { s: ['second', 'seconds'], m: ['minute', 'minutes'], h: ['hour', 'hours'], d: ['day', 'days'] };
|
|
38
|
+
const WEEKDAYS = ['Sundays', 'Mondays', 'Tuesdays', 'Wednesdays', 'Thursdays', 'Fridays', 'Saturdays'];
|
|
39
|
+
export function describeTemplateSchedule(schedule) {
|
|
40
|
+
if (schedule.at && schedule.weekday !== undefined && schedule.every === '7d')
|
|
41
|
+
return `${WEEKDAYS[schedule.weekday]} at ${schedule.at}`;
|
|
42
|
+
if (schedule.at && schedule.every === '1d')
|
|
43
|
+
return `daily at ${schedule.at}`;
|
|
44
|
+
const match = /^(\d+)([smhd])$/.exec(schedule.every);
|
|
45
|
+
if (!match)
|
|
46
|
+
return `every ${schedule.every}`;
|
|
47
|
+
const n = Number(match[1]);
|
|
48
|
+
const [one, many] = UNITS[match[2]];
|
|
49
|
+
return `every ${n === 1 ? one : `${n} ${many}`}${schedule.at ? `, from ${schedule.at}` : ''}`;
|
|
50
|
+
}
|
|
18
51
|
export function listTemplates() {
|
|
19
|
-
return STARTER_TEMPLATES.map(({ id, category, candidate: c }) => ({
|
|
52
|
+
return STARTER_TEMPLATES.map(({ id, category, candidate: c, setup }) => ({
|
|
20
53
|
id,
|
|
21
54
|
category,
|
|
22
55
|
title: c.title,
|
|
23
56
|
summary: c.summary,
|
|
24
|
-
trigger: c.trigger.description,
|
|
57
|
+
trigger: setup.schedule ? describeTemplateSchedule(setup.schedule) : c.trigger.description,
|
|
25
58
|
asksFirst: c.asksFirst,
|
|
26
|
-
|
|
27
|
-
schedule:
|
|
59
|
+
abilities: setup.abilities,
|
|
60
|
+
schedule: setup.schedule ? describeTemplateSchedule(setup.schedule) : null,
|
|
28
61
|
}));
|
|
29
62
|
}
|
|
63
|
+
function keyGroups(template) {
|
|
64
|
+
return template.setup.keys.map((k) => (Array.isArray(k) ? k : [k]));
|
|
65
|
+
}
|
|
30
66
|
export function previewTemplate(id, deps) {
|
|
31
|
-
|
|
67
|
+
const template = templateAt(id);
|
|
68
|
+
const name = freeAgentName(template.candidate.agent.name, deps.agentExists);
|
|
69
|
+
const envValue = deps.envValue ?? (() => undefined);
|
|
70
|
+
const { setup } = template;
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
systemPrompt: template.candidate.agent.systemPrompt,
|
|
74
|
+
abilities: setup.abilities,
|
|
75
|
+
skills: (setup.skills ?? []).map((s) => s.id),
|
|
76
|
+
rules: setup.rules.map((r) => ({ tool: r.tool, decision: r.decision })),
|
|
77
|
+
// A key set for the whole project already counts for a new agent.
|
|
78
|
+
keys: keyGroups(template).map((names) => ({ names, set: names.some((n) => Boolean(envValue(name, n))) })),
|
|
79
|
+
settings: setup.env ?? {},
|
|
80
|
+
schedule: setup.schedule ? `${describeTemplateSchedule(setup.schedule)} (created paused)` : null,
|
|
81
|
+
needsApprovals: setup.needsApprovals,
|
|
82
|
+
notes: setup.notes ?? [],
|
|
83
|
+
};
|
|
32
84
|
}
|
|
33
|
-
/**
|
|
34
|
-
|
|
85
|
+
/** What's left once the agent exists, each linked to where it's done. */
|
|
86
|
+
export function templateChecklist(template, name, deps, failed = []) {
|
|
87
|
+
const tab = (t) => `/agents/config?agent=${encodeURIComponent(name)}&tab=${t}`;
|
|
88
|
+
const items = [];
|
|
89
|
+
for (const f of failed)
|
|
90
|
+
items.push({ label: `Install the ${f.name} ability (it couldn't be installed: ${f.error})`, href: tab('abilities') });
|
|
91
|
+
const missing = keyGroups(template).filter((names) => !names.some((n) => deps.envValue(name, n)));
|
|
92
|
+
const single = missing.filter((g) => g.length === 1).map((g) => g[0]);
|
|
93
|
+
if (single.length)
|
|
94
|
+
items.push({ label: `Add ${single.length === 1 ? 'its key' : 'its keys'}: ${single.join(', ')}`, href: tab('env') });
|
|
95
|
+
for (const group of missing.filter((g) => g.length > 1)) {
|
|
96
|
+
items.push({ label: `Add one of ${group.slice(0, -1).join(', ')} or ${group.at(-1)}`, href: tab('env') });
|
|
97
|
+
}
|
|
98
|
+
if (template.setup.needsApprovals && !deps.messagingConfigured(name)) {
|
|
99
|
+
items.push({ label: 'Choose where approval requests go: Slack, email, Lark or a webhook', href: tab('messaging') });
|
|
100
|
+
}
|
|
101
|
+
for (const note of template.setup.notes ?? [])
|
|
102
|
+
items.push({ label: note });
|
|
103
|
+
if (template.setup.schedule) {
|
|
104
|
+
items.push({ label: `Turn on its schedule (${describeTemplateSchedule(template.setup.schedule)}) once it works in Chat`, href: tab('schedules') });
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
}
|
|
108
|
+
/** Creates a new agent from the template, with its whole setup, under the
|
|
109
|
+
* first free name, so the same template can be used more than once. */
|
|
35
110
|
export async function createFromTemplate(id, deps) {
|
|
36
|
-
|
|
111
|
+
if (!('scaffoldAgent' in deps))
|
|
112
|
+
return createFromTemplateLegacy(id, deps);
|
|
113
|
+
const template = templateAt(id);
|
|
114
|
+
const { candidate, setup } = template;
|
|
115
|
+
const name = freeAgentName(candidate.agent.name, deps.agentExists);
|
|
116
|
+
// 1. The agent, and its permissions: everything asks unless a rule says
|
|
117
|
+
// otherwise, and the template's rules come first.
|
|
118
|
+
await deps.scaffoldAgent(name, candidate.agent.systemPrompt);
|
|
119
|
+
setDefaultDecision(name, 'ask');
|
|
120
|
+
for (const rule of setup.rules) {
|
|
121
|
+
try {
|
|
122
|
+
addActauthRule(name, { name: rule.name, scope: '*/*', tool: rule.tool, decision: rule.decision });
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (!(err instanceof ActauthRuleExistsError))
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// 2. Abilities: their tools, skills and rules. One failing doesn't stop
|
|
130
|
+
// the rest; it heads the checklist.
|
|
131
|
+
const installed = [];
|
|
132
|
+
const failed = [];
|
|
133
|
+
for (const spec of setup.abilities) {
|
|
134
|
+
if (!deps.installAbility) {
|
|
135
|
+
failed.push({ name: spec, error: 'installing abilities needs LOOPENGINE_ADMIN_AUTH set on the server' });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await deps.installAbility(name, spec);
|
|
140
|
+
installed.push(spec);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
failed.push({ name: spec, error: installFailureReason(err) });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 3. Its own skills and non-secret settings.
|
|
147
|
+
for (const skill of setup.skills ?? [])
|
|
148
|
+
writeSkill(name, skill.id, { description: skill.description, body: skill.body });
|
|
149
|
+
for (const [variable, value] of Object.entries(setup.env ?? {}))
|
|
150
|
+
await setAgentEnvVar(name, variable, value);
|
|
151
|
+
// 4. Load it, now that everything is in place.
|
|
152
|
+
let loadError;
|
|
153
|
+
try {
|
|
154
|
+
await deps.registerAgent(name);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
loadError = err instanceof Error ? err.message : String(err);
|
|
158
|
+
}
|
|
159
|
+
// 5. Its schedule, paused until the person turns it on.
|
|
160
|
+
let schedule;
|
|
161
|
+
let scheduleError;
|
|
162
|
+
if (setup.schedule) {
|
|
163
|
+
try {
|
|
164
|
+
const startAt = templateScheduleStart(setup.schedule);
|
|
165
|
+
const task = await createScheduledTask({
|
|
166
|
+
agent: name,
|
|
167
|
+
message: setup.schedule.message,
|
|
168
|
+
schedule: { every: setup.schedule.every, ...(startAt ? { start_at: startAt } : {}) },
|
|
169
|
+
task_name: setup.schedule.name,
|
|
170
|
+
});
|
|
171
|
+
await pauseScheduledTask(task.task_id);
|
|
172
|
+
schedule = { description: describeTemplateSchedule(setup.schedule), timeZone: workspaceTimeZone(), ...(startAt ? { next_run_at: startAt } : {}) };
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
scheduleError = err instanceof Error ? err.message : String(err);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
name,
|
|
180
|
+
abilities: { installed, failed },
|
|
181
|
+
checklist: templateChecklist(template, name, deps, failed),
|
|
182
|
+
...(candidate.agent.exampleMessage ? { exampleMessage: candidate.agent.exampleMessage } : {}),
|
|
183
|
+
...(schedule ? { schedule } : {}),
|
|
184
|
+
...(scheduleError ? { scheduleError } : {}),
|
|
185
|
+
...(loadError ? { loadError } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** An older project adapter's create: the agent with its instructions,
|
|
189
|
+
* permissions and paused schedule, as before; its abilities head the
|
|
190
|
+
* checklist with how to get them installed automatically. */
|
|
191
|
+
async function createFromTemplateLegacy(id, deps) {
|
|
192
|
+
const template = templateAt(id);
|
|
193
|
+
const { candidate, setup } = template;
|
|
194
|
+
const name = freeAgentName(candidate.agent.name, deps.agentExists);
|
|
195
|
+
await deps.createAgent(name, candidate.agent.systemPrompt);
|
|
196
|
+
setDefaultDecision(name, 'ask');
|
|
197
|
+
for (const rule of setup.rules) {
|
|
198
|
+
try {
|
|
199
|
+
addActauthRule(name, { name: rule.name, scope: '*/*', tool: rule.tool, decision: rule.decision });
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (!(err instanceof ActauthRuleExistsError))
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const failed = setup.abilities.map((spec) => ({ name: spec, error: "this project's server is from an older LoopEngine template; update it from Settings → Upgrade to have templates install abilities" }));
|
|
207
|
+
let schedule;
|
|
208
|
+
let scheduleError;
|
|
209
|
+
if (setup.schedule) {
|
|
210
|
+
try {
|
|
211
|
+
const startAt = templateScheduleStart(setup.schedule);
|
|
212
|
+
const task = await createScheduledTask({ agent: name, message: setup.schedule.message, schedule: { every: setup.schedule.every, ...(startAt ? { start_at: startAt } : {}) }, task_name: setup.schedule.name });
|
|
213
|
+
await pauseScheduledTask(task.task_id);
|
|
214
|
+
schedule = { description: describeTemplateSchedule(setup.schedule), timeZone: workspaceTimeZone(), ...(startAt ? { next_run_at: startAt } : {}) };
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
scheduleError = err instanceof Error ? err.message : String(err);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
name,
|
|
222
|
+
abilities: { installed: [], failed },
|
|
223
|
+
checklist: templateChecklist(template, name, { envValue: () => undefined, messagingConfigured: () => false }, failed),
|
|
224
|
+
...(candidate.agent.exampleMessage ? { exampleMessage: candidate.agent.exampleMessage } : {}),
|
|
225
|
+
...(schedule ? { schedule } : {}),
|
|
226
|
+
...(scheduleError ? { scheduleError } : {}),
|
|
227
|
+
};
|
|
37
228
|
}
|
|
@@ -63,23 +63,32 @@ ${consoleShellScript}
|
|
|
63
63
|
}).join('') + '</ul>';
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
var canInstall = true;
|
|
67
|
+
var DECISIONS = { allow: 'runs on its own', ask: 'asks you first', deny: 'never' };
|
|
68
|
+
var CHECK = String.fromCharCode(10003);
|
|
69
|
+
|
|
66
70
|
function cardHtml(t) {
|
|
67
71
|
return '<div class="tp-card" data-id="' + esc(t.id) + '">' +
|
|
68
72
|
'<span class="tp-cat">' + esc(t.category) + '</span>' +
|
|
69
73
|
'<h3>' + esc(t.title) + '</h3>' +
|
|
70
74
|
'<div>' + esc(t.summary) + '</div>' +
|
|
71
75
|
'<div class="meta">' +
|
|
72
|
-
'<div><strong>
|
|
76
|
+
'<div><strong>Runs:</strong> ' + esc(t.trigger) + '</div>' +
|
|
73
77
|
(t.asksFirst.length ? '<div><strong>Asks you first:</strong> ' + esc(t.asksFirst.join(', ')) + '</div>' : '') +
|
|
74
|
-
|
|
78
|
+
'<div><strong>Installs:</strong> ' + t.abilities.map(function (a) { return '<code>' + esc(a) + '</code>'; }).join(' ') + '</div>' +
|
|
75
79
|
'</div>' +
|
|
76
80
|
'<div class="row"><button type="button" class="primary tp-use">Use this template</button></div>' +
|
|
77
81
|
'<div class="tp-slot"></div></div>';
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function listHtml(items) {
|
|
85
|
+
return '<ul style="margin:4px 0 0;padding-left:18px">' + items.join('') + '</ul>';
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
function show() {
|
|
81
89
|
api('GET', '/starter-templates').then(function (r) {
|
|
82
90
|
if (!r.ok) { page.innerHTML = '<p class="error">' + esc(r.body.error || 'Could not load the templates') + '</p>'; return; }
|
|
91
|
+
canInstall = r.body.canInstallAbilities !== false;
|
|
83
92
|
page.innerHTML = '<div class="tp-grid">' + r.body.templates.map(cardHtml).join('') + '</div>' +
|
|
84
93
|
'<p class="hint" style="margin-top:16px">Not what you need? <a href="/interview">Find your first agent</a> suggests one from your own work, or <a href="/agents?new=1">start from a blank agent</a>.</p>';
|
|
85
94
|
Array.prototype.forEach.call(page.querySelectorAll('.tp-use'), function (btn) {
|
|
@@ -95,24 +104,40 @@ ${consoleShellScript}
|
|
|
95
104
|
api('GET', '/starter-templates/' + encodeURIComponent(id) + '/preview').then(function (r) {
|
|
96
105
|
if (!r.ok) { slot.innerHTML = '<p class="error">' + esc(r.body.error || 'Could not prepare it') + '</p>'; return; }
|
|
97
106
|
var p = r.body;
|
|
107
|
+
var settings = Object.keys(p.settings || {});
|
|
98
108
|
slot.innerHTML = '<div class="tp-preview">' +
|
|
99
109
|
'<div><strong>Agent:</strong> <code>' + esc(p.name) + '</code></div>' +
|
|
100
110
|
'<div><strong>Instructions</strong> <span class="hint">(edit them later in its Overview tab)</span><pre>' + esc(p.systemPrompt) + '</pre></div>' +
|
|
101
|
-
'<div><strong>
|
|
111
|
+
'<div><strong>Installs</strong> <span class="hint">(abilities: their tools, skills and permission rules)</span>' +
|
|
112
|
+
listHtml(p.abilities.map(function (a) { return '<li><code>' + esc(a) + '</code></li>'; })) +
|
|
113
|
+
(canInstall ? '' : '<p class="error" style="margin:4px 0 0">Installing abilities needs <code>LOOPENGINE_ADMIN_AUTH</code> set on the server. Without it, the agent is created and its abilities head the checklist.</p>') + '</div>' +
|
|
114
|
+
'<div><strong>Permissions</strong> <span class="hint">(anything not listed asks you first)</span>' +
|
|
115
|
+
listHtml(p.rules.map(function (rule) { return '<li><code>' + esc(rule.tool) + '</code>: ' + esc(DECISIONS[rule.decision] || rule.decision) + '</li>'; })) + '</div>' +
|
|
116
|
+
'<div><strong>Keys it needs</strong>' +
|
|
117
|
+
listHtml(p.keys.map(function (k) {
|
|
118
|
+
var names = k.names.length > 1 ? 'one of ' + k.names.join(', ') : k.names[0];
|
|
119
|
+
return '<li>' + esc(names) + (k.set ? ' <span class="hint">' + CHECK + ' already set</span>' : '') + '</li>';
|
|
120
|
+
})) + '</div>' +
|
|
121
|
+
(settings.length ? '<div><strong>Settings filled in:</strong> ' + settings.map(function (s) { return '<code>' + esc(s) + '=' + esc(p.settings[s]) + '</code>'; }).join(', ') + '</div>' : '') +
|
|
102
122
|
(p.schedule ? '<div><strong>Schedule:</strong> ' + esc(p.schedule) + '</div>' : '') +
|
|
103
|
-
'<div><strong>
|
|
123
|
+
(p.needsApprovals ? '<div><strong>Approvals:</strong> requests reach you wherever its Messaging tab sends them (Slack, email, Lark or a webhook).</div>' : '') +
|
|
124
|
+
(p.notes.length ? '<div><strong>You also</strong>' + listHtml(p.notes.map(function (n) { return '<li>' + esc(n) + '</li>'; })) + '</div>' : '') +
|
|
104
125
|
'<div class="row"><button type="button" class="primary tp-create">Create it</button><button type="button" class="tp-cancel">Cancel</button></div>' +
|
|
105
126
|
'<div class="hint tp-status"></div></div>';
|
|
106
127
|
slot.querySelector('.tp-cancel').addEventListener('click', function () { slot.innerHTML = ''; });
|
|
107
128
|
slot.querySelector('.tp-create').addEventListener('click', function (ev) {
|
|
108
129
|
var btn = ev.currentTarget;
|
|
109
130
|
btn.disabled = true;
|
|
110
|
-
slot.querySelector('.tp-status').textContent = 'Creating' + ELLIPSIS;
|
|
131
|
+
slot.querySelector('.tp-status').textContent = 'Creating it and installing its abilities' + ELLIPSIS + ' (this can take a minute)';
|
|
111
132
|
api('POST', '/starter-templates/' + encodeURIComponent(id) + '/create').then(function (r2) {
|
|
112
133
|
if (!r2.ok) { btn.disabled = false; slot.querySelector('.tp-status').textContent = r2.body.error || 'Could not create it'; return; }
|
|
113
134
|
var c = r2.body;
|
|
135
|
+
var installed = c.abilities.installed.map(function (a) { return '<code>' + esc(a) + '</code>'; }).join(' ');
|
|
114
136
|
slot.innerHTML = '<div class="tp-preview">' +
|
|
115
|
-
'<div><strong>Created <code>' + esc(c.name) + '</code>.</strong>
|
|
137
|
+
'<div><strong>Created <code>' + esc(c.name) + '</code>.</strong> Anything its permissions don' + String.fromCharCode(8217) + 't cover asks you first.</div>' +
|
|
138
|
+
(installed ? '<div>' + CHECK + ' Installed ' + installed + '</div>' : '') +
|
|
139
|
+
(c.schedule ? '<div>' + CHECK + ' Schedule created, paused: ' + esc(c.schedule.description) + (c.schedule.next_run_at ? ' (first run ' + esc(new Date(c.schedule.next_run_at).toLocaleString(undefined, c.schedule.timeZone ? { timeZone: c.schedule.timeZone, timeZoneName: 'short' } : undefined)) + ' once turned on)' : '') + '</div>' : '') +
|
|
140
|
+
(c.loadError ? '<div class="error">It was written but couldn' + String.fromCharCode(8217) + 't be loaded: ' + esc(c.loadError) + '</div>' : '') +
|
|
116
141
|
(c.scheduleError ? '<div class="error">Its schedule wasn' + String.fromCharCode(8217) + 't created: ' + esc(c.scheduleError) + '. Add one in its Schedules tab.</div>' : '') +
|
|
117
142
|
'<div><strong>Finish setup:</strong>' + checklistHtml(c.checklist) + '</div>' +
|
|
118
143
|
(c.exampleMessage ? '<div><strong>Try it</strong> in Chat with: <em>' + esc(c.exampleMessage) + '</em></div>' : '') +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.94",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
"dist/core/trusted-proxy.d.ts",
|
|
47
47
|
"dist/core/usage.js",
|
|
48
48
|
"dist/core/usage.d.ts",
|
|
49
|
+
"dist/core/email-senders.js",
|
|
50
|
+
"dist/core/email-senders.d.ts",
|
|
49
51
|
"dist/core/notifier-file.js",
|
|
50
52
|
"dist/core/notifier-file.d.ts",
|
|
51
53
|
"dist/core/notifier-deliveries.js",
|
|
@@ -55,6 +57,8 @@
|
|
|
55
57
|
"dist/core/messaging",
|
|
56
58
|
"dist/core/scheduler.js",
|
|
57
59
|
"dist/core/scheduler.d.ts",
|
|
60
|
+
"dist/core/time-zone.js",
|
|
61
|
+
"dist/core/time-zone.d.ts",
|
|
58
62
|
"dist/core/session-store.js",
|
|
59
63
|
"dist/core/session-store.d.ts",
|
|
60
64
|
"dist/core/vector-index.js",
|