aimeat 1.18.0 → 1.19.0
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 +22 -1
- package/dist/locales/en.json +28 -0
- package/dist/locales/fi.json +28 -0
- package/dist/public/css/views/agents-detail.css +145 -0
- package/dist/public/css/views/scheduler.css +16 -0
- package/dist/public/js/services/admin.js +3 -0
- package/dist/public/llms-template.txt +170 -2
- package/dist/public/oauth-consent.html +7 -1
- package/dist/public/views/admin/portal-tab.js +94 -9
- package/dist/public/views/profile/agents/agent-card.js +24 -4
- package/dist/public/views/profile/agents/tab-schedules.js +3 -32
- package/dist/public/views/profile/agents/tab-tasks.js +5 -2
- package/dist/public/views/profile/agents-tab.js +86 -3
- package/dist/public/views/profile/agents-tasks-subtab.js +71 -7
- package/dist/public/views/profile/schedule-item.js +177 -0
- package/dist/public/views/profile/scheduler-tab.js +5 -51
- package/dist/src/generated/api-types.d.ts +277 -2
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/mcp/catalog/scopes.js +1 -1
- package/dist/src/mcp/catalog/scopes.js.map +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js +12 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/routes/bootstrap.d.ts +2 -1
- package/dist/src/routes/bootstrap.d.ts.map +1 -1
- package/dist/src/routes/bootstrap.js +14 -3
- package/dist/src/routes/bootstrap.js.map +1 -1
- package/dist/src/routes/cortex.d.ts +13 -0
- package/dist/src/routes/cortex.d.ts.map +1 -1
- package/dist/src/routes/cortex.js +211 -0
- package/dist/src/routes/cortex.js.map +1 -1
- package/dist/src/routes/extensions.d.ts +14 -0
- package/dist/src/routes/extensions.d.ts.map +1 -1
- package/dist/src/routes/extensions.js +255 -109
- package/dist/src/routes/extensions.js.map +1 -1
- package/dist/src/routes/lib-header.d.ts +22 -0
- package/dist/src/routes/lib-header.d.ts.map +1 -0
- package/dist/src/routes/lib-header.js +244 -0
- package/dist/src/routes/lib-header.js.map +1 -0
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +13 -0
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/schedules.d.ts.map +1 -1
- package/dist/src/routes/schedules.js +19 -5
- package/dist/src/routes/schedules.js.map +1 -1
- package/dist/src/routes/site.d.ts.map +1 -1
- package/dist/src/routes/site.js +39 -1
- package/dist/src/routes/site.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +5 -2
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/services/prompt-defaults.d.ts.map +1 -1
- package/dist/src/services/prompt-defaults.js +35 -0
- package/dist/src/services/prompt-defaults.js.map +1 -1
- package/dist/src/services/prompt-seeder.d.ts.map +1 -1
- package/dist/src/services/prompt-seeder.js +6 -2
- package/dist/src/services/prompt-seeder.js.map +1 -1
- package/dist/src/services/site.d.ts +4 -0
- package/dist/src/services/site.d.ts.map +1 -1
- package/dist/src/services/site.js +29 -0
- package/dist/src/services/site.js.map +1 -1
- package/dist/src/storage/interface.d.ts +1 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/utils/csp-nonce.d.ts +19 -0
- package/dist/src/utils/csp-nonce.d.ts.map +1 -0
- package/dist/src/utils/csp-nonce.js +25 -0
- package/dist/src/utils/csp-nonce.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file schedule-item.js
|
|
3
|
+
* @description Shared, editable card for one managed schedule — used by both the
|
|
4
|
+
* Profile › Scheduler master view and the per-agent Schedules sub-tab. Shows the
|
|
5
|
+
* schedule meta (kind, cron, timezone, agent, last/next run, runs), the full
|
|
6
|
+
* "what it dispatches each run" content on its own row (agent_task title+desc,
|
|
7
|
+
* ai prompt+keys, or extension action), and an inline editor (cron, name,
|
|
8
|
+
* timezone, purpose + the kind-specific payload). Handles pause/resume, run-now,
|
|
9
|
+
* edit (PATCH), and cancel itself; calls onChanged() to let the parent refetch.
|
|
10
|
+
* @version-history
|
|
11
|
+
* v1.0.0 -- 2026-06-03 -- Initial editable schedule card
|
|
12
|
+
*/
|
|
13
|
+
import { h } from 'preact';
|
|
14
|
+
import { useState } from 'preact/hooks';
|
|
15
|
+
import htm from 'htm';
|
|
16
|
+
import { t } from '/js/i18n.js';
|
|
17
|
+
import { timeAgo } from '/js/utils.js';
|
|
18
|
+
import { setScheduleEnabled, triggerSchedule, deleteSchedule, updateSchedule } from '/js/services/schedules.js';
|
|
19
|
+
|
|
20
|
+
const html = htm.bind(h);
|
|
21
|
+
|
|
22
|
+
const KIND_BADGE = {
|
|
23
|
+
ai: 'sch-badge--ai', agent_task: 'sch-badge--agent', extension: 'sch-badge--ext', core: 'sch-badge--core',
|
|
24
|
+
};
|
|
25
|
+
const KIND_LABEL = {
|
|
26
|
+
ai: 'profile.scheduler.kind.ai', agent_task: 'profile.scheduler.kind.agent_task',
|
|
27
|
+
extension: 'profile.scheduler.kind.extension', core: 'profile.scheduler.kind.core',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Human "time until" for a future ISO timestamp (e.g. "20h 48min", "45min 30s"). */
|
|
31
|
+
export function formatUntil(iso) {
|
|
32
|
+
if (!iso) return '—';
|
|
33
|
+
const ms = new Date(iso).getTime() - Date.now();
|
|
34
|
+
if (Number.isNaN(ms)) return '—';
|
|
35
|
+
if (ms <= 0) return t('profile.scheduler.soon');
|
|
36
|
+
const s = Math.floor(ms / 1000);
|
|
37
|
+
const d = Math.floor(s / 86400);
|
|
38
|
+
const h = Math.floor((s % 86400) / 3600);
|
|
39
|
+
const m = Math.floor((s % 3600) / 60);
|
|
40
|
+
const sec = s % 60;
|
|
41
|
+
const parts = [];
|
|
42
|
+
if (d) parts.push(d + 'd');
|
|
43
|
+
if (h) parts.push(h + 'h');
|
|
44
|
+
if (m) parts.push(m + 'min');
|
|
45
|
+
if (!d && !h) parts.push(sec + 's'); // seconds only when under an hour
|
|
46
|
+
return parts.join(' ') || '0s';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What a schedule produces each fire (title + body) for display. */
|
|
50
|
+
function describeDispatch(s) {
|
|
51
|
+
if (s.type === 'agent_task') {
|
|
52
|
+
const tmpl = (s.input && s.input.taskTemplate) || {};
|
|
53
|
+
return { title: tmpl.title || '', body: tmpl.description || '' };
|
|
54
|
+
}
|
|
55
|
+
if (s.type === 'ai') {
|
|
56
|
+
const c = s.input || {};
|
|
57
|
+
const io = `${(c.inputKeys || []).join(', ') || '—'} → ${c.outputKey || t('profile.scheduler.autoKey')}`;
|
|
58
|
+
return { title: c.prompt || '', body: io };
|
|
59
|
+
}
|
|
60
|
+
if (s.type === 'extension') {
|
|
61
|
+
return { title: `${s.extensionName || ''}${s.actionId ? ' / ' + s.actionId : ''}`, body: '' };
|
|
62
|
+
}
|
|
63
|
+
return { title: '', body: '' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default function ScheduleItem({ schedule: s, onChanged, showToast }) {
|
|
67
|
+
const [editing, setEditing] = useState(false);
|
|
68
|
+
const [busy, setBusy] = useState(false);
|
|
69
|
+
const [f, setF] = useState(() => {
|
|
70
|
+
const c = s.input || {};
|
|
71
|
+
const tmpl = c.taskTemplate || {};
|
|
72
|
+
return {
|
|
73
|
+
display_name: s.displayName || '', cron: s.cron || '', timezone: s.timezone || '', purpose: s.purpose || '',
|
|
74
|
+
task_title: tmpl.title || '', task_description: tmpl.description || '',
|
|
75
|
+
prompt: c.prompt || '', input_keys: (c.inputKeys || []).join(', '), output_key: c.outputKey || '',
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const set = (k, v) => setF(prev => ({ ...prev, [k]: v }));
|
|
79
|
+
|
|
80
|
+
const run = async (fn) => {
|
|
81
|
+
setBusy(true);
|
|
82
|
+
try { await fn(); onChanged?.(); } catch (e) { showToast?.(e.message, true); } finally { setBusy(false); }
|
|
83
|
+
};
|
|
84
|
+
const onToggle = () => run(() => setScheduleEnabled(s.id, !s.enabled));
|
|
85
|
+
const onTrigger = () => run(async () => { await triggerSchedule(s.id); showToast?.(t('profile.scheduler.triggered')); });
|
|
86
|
+
const onCancel = () => { if (window.confirm(t('profile.scheduler.confirmCancel'))) run(() => deleteSchedule(s.id)); };
|
|
87
|
+
|
|
88
|
+
const onSave = async () => {
|
|
89
|
+
const patch = {
|
|
90
|
+
display_name: f.display_name.trim(), cron: f.cron.trim(),
|
|
91
|
+
timezone: f.timezone.trim() || undefined, purpose: f.purpose.trim() || undefined,
|
|
92
|
+
};
|
|
93
|
+
if (s.type === 'agent_task') {
|
|
94
|
+
patch.input = { ...(s.input || {}), taskTemplate: { title: f.task_title.trim(), description: f.task_description.trim() } };
|
|
95
|
+
} else if (s.type === 'ai') {
|
|
96
|
+
patch.input = {
|
|
97
|
+
...(s.input || {}),
|
|
98
|
+
prompt: f.prompt.trim(),
|
|
99
|
+
inputKeys: f.input_keys.split(',').map(x => x.trim()).filter(Boolean),
|
|
100
|
+
outputKey: f.output_key.trim() || undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
setBusy(true);
|
|
104
|
+
try { await updateSchedule(s.id, patch); showToast?.(t('profile.scheduler.saved')); setEditing(false); onChanged?.(); }
|
|
105
|
+
catch (e) { showToast?.(e.message, true); }
|
|
106
|
+
finally { setBusy(false); }
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const d = describeDispatch(s);
|
|
110
|
+
|
|
111
|
+
return html`
|
|
112
|
+
<div class="sch-card">
|
|
113
|
+
<div class="sch-card-head">
|
|
114
|
+
<div class="sch-card-title">
|
|
115
|
+
<span class="sch-badge ${KIND_BADGE[s.type] || KIND_BADGE.core}">${t(KIND_LABEL[s.type] || KIND_LABEL.core)}</span>
|
|
116
|
+
<span class="sch-name">${s.displayName || s.name}</span>
|
|
117
|
+
${s.createdByAgent ? html`<span class="sch-tag">${t('profile.scheduler.byAgent')}</span>` : null}
|
|
118
|
+
</div>
|
|
119
|
+
<div class="sch-actions">
|
|
120
|
+
<button class="btn-ghost sch-btn" disabled=${busy} onClick=${onToggle}>${s.enabled ? t('profile.scheduler.pause') : t('profile.scheduler.resume')}</button>
|
|
121
|
+
<button class="btn-ghost sch-btn" disabled=${busy} onClick=${onTrigger}>${t('profile.scheduler.runNow')}</button>
|
|
122
|
+
<button class="btn-ghost sch-btn" disabled=${busy} onClick=${() => setEditing(e => !e)}>${editing ? t('profile.scheduler.close') : t('profile.scheduler.edit')}</button>
|
|
123
|
+
<button class="btn-danger sch-btn" disabled=${busy} onClick=${onCancel}>${t('profile.scheduler.cancel')}</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="sch-card-meta">
|
|
128
|
+
<span><span class="sch-meta-k">${t('profile.scheduler.col.cron')}:</span> <code class="sch-cron">${s.cron}</code>${s.timezone ? ' · ' + s.timezone : ''}</span>
|
|
129
|
+
${s.agentName ? html`<span><span class="sch-meta-k">${t('profile.scheduler.col.agent')}:</span> ${s.agentName}</span>` : null}
|
|
130
|
+
<span><span class="sch-meta-k">${t('profile.scheduler.col.lastRun')}:</span> ${s.lastRunAt ? timeAgo(s.lastRunAt) : t('profile.scheduler.never')}${s.lastRunResult ? html` <span class="sch-badge ${s.lastRunResult === 'error' ? 'sch-badge--err' : 'sch-badge--ok'}">${s.lastRunResult}</span>` : ''}</span>
|
|
131
|
+
<span><span class="sch-meta-k">${t('profile.scheduler.col.nextRun')}:</span> ${s.enabled ? formatUntil(s.nextRunAt) : t('profile.scheduler.paused')}</span>
|
|
132
|
+
<span><span class="sch-meta-k">${t('profile.scheduler.col.runs')}:</span> ${s.runCount ?? 0}</span>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
${(d.title || d.body) && html`
|
|
136
|
+
<div class="sch-card-dispatch">
|
|
137
|
+
<div class="sch-dispatch-label">${t('profile.scheduler.dispatches')}</div>
|
|
138
|
+
${d.title ? html`<div class="sch-dispatch-title">${d.title}</div>` : null}
|
|
139
|
+
${d.body ? html`<div class="sch-dispatch-body">${d.body}</div>` : null}
|
|
140
|
+
</div>`}
|
|
141
|
+
|
|
142
|
+
${s.purpose && !editing ? html`<div class="sch-muted sch-purpose">${s.purpose}</div>` : null}
|
|
143
|
+
|
|
144
|
+
${editing && html`
|
|
145
|
+
<div class="sch-edit">
|
|
146
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.displayName')}</label>
|
|
147
|
+
<input type="text" value=${f.display_name} onInput=${e => set('display_name', e.target.value)} /></div>
|
|
148
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.schedule')} (cron)</label>
|
|
149
|
+
<input type="text" class="sch-cron-input" value=${f.cron} onInput=${e => set('cron', e.target.value)} placeholder="0 7 * * *" /></div>
|
|
150
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.ph.timezone')}</label>
|
|
151
|
+
<input type="text" value=${f.timezone} onInput=${e => set('timezone', e.target.value)} placeholder="Europe/Helsinki" /></div>
|
|
152
|
+
|
|
153
|
+
${s.type === 'agent_task' && html`
|
|
154
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.taskTitle')}</label>
|
|
155
|
+
<input type="text" value=${f.task_title} onInput=${e => set('task_title', e.target.value)} /></div>
|
|
156
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.taskDescription')}</label>
|
|
157
|
+
<textarea rows="3" value=${f.task_description} onInput=${e => set('task_description', e.target.value)}></textarea></div>
|
|
158
|
+
`}
|
|
159
|
+
${s.type === 'ai' && html`
|
|
160
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.inputKeys')}</label>
|
|
161
|
+
<input type="text" value=${f.input_keys} onInput=${e => set('input_keys', e.target.value)} placeholder=${t('profile.scheduler.ph.inputKeys')} /></div>
|
|
162
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.prompt')}</label>
|
|
163
|
+
<textarea rows="3" value=${f.prompt} onInput=${e => set('prompt', e.target.value)}></textarea></div>
|
|
164
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.outputKey')}</label>
|
|
165
|
+
<input type="text" value=${f.output_key} onInput=${e => set('output_key', e.target.value)} placeholder=${t('profile.scheduler.ph.outputKey')} /></div>
|
|
166
|
+
`}
|
|
167
|
+
|
|
168
|
+
<div class="sch-form-row"><label>${t('profile.scheduler.field.purpose')}</label>
|
|
169
|
+
<input type="text" value=${f.purpose} onInput=${e => set('purpose', e.target.value)} /></div>
|
|
170
|
+
|
|
171
|
+
<div class="sch-form-actions">
|
|
172
|
+
<button class="btn-primary btn-sm" disabled=${busy} onClick=${onSave}>${busy ? t('profile.scheduler.saving') : t('profile.scheduler.save')}</button>
|
|
173
|
+
<button class="btn-outline btn-sm" disabled=${busy} onClick=${() => setEditing(false)}>${t('profile.scheduler.close')}</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>`}
|
|
176
|
+
</div>`;
|
|
177
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @usage Registered in profile.js TABS as { id:'scheduler', component: SchedulerTab }.
|
|
11
11
|
* @version-history
|
|
12
12
|
* v1.0.0 -- 2026-06-03 -- Initial master scheduler view
|
|
13
|
+
* v1.0.1 -- 2026-06-03 -- Extension cron "Next run" uses formatUntil (was timeAgo → negative "ago")
|
|
13
14
|
*/
|
|
14
15
|
import { h } from 'preact';
|
|
15
16
|
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
@@ -17,10 +18,8 @@ import htm from 'htm';
|
|
|
17
18
|
import { t } from '/js/i18n.js';
|
|
18
19
|
import { timeAgo } from '/js/utils.js';
|
|
19
20
|
import { listAgents } from '/js/services/agents.js';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
triggerSchedule, deleteSchedule,
|
|
23
|
-
} from '/js/services/schedules.js';
|
|
21
|
+
import { listAllSchedules, createSchedule } from '/js/services/schedules.js';
|
|
22
|
+
import ScheduleItem, { formatUntil } from './schedule-item.js';
|
|
24
23
|
|
|
25
24
|
const html = htm.bind(h);
|
|
26
25
|
|
|
@@ -79,42 +78,6 @@ export default function SchedulerTab({ showToast }) {
|
|
|
79
78
|
return () => { window.removeEventListener('aimeat-live-update', handler); clearInterval(poller); };
|
|
80
79
|
}, [loadData]);
|
|
81
80
|
|
|
82
|
-
const onToggle = async (job) => {
|
|
83
|
-
try { await setScheduleEnabled(job.id, !job.enabled); await loadData(); }
|
|
84
|
-
catch (e) { showToast?.(e.message, true); }
|
|
85
|
-
};
|
|
86
|
-
const onTrigger = async (job) => {
|
|
87
|
-
try { await triggerSchedule(job.id); showToast?.(t('profile.scheduler.triggered')); await loadData(); }
|
|
88
|
-
catch (e) { showToast?.(e.message, true); }
|
|
89
|
-
};
|
|
90
|
-
const onCancel = async (job) => {
|
|
91
|
-
if (!window.confirm(t('profile.scheduler.confirmCancel'))) return;
|
|
92
|
-
try { await deleteSchedule(job.id); showToast?.(t('profile.scheduler.cancelled')); await loadData(); }
|
|
93
|
-
catch (e) { showToast?.(e.message, true); }
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const renderManagedRow = (job) => html`
|
|
97
|
-
<tr key=${job.id}>
|
|
98
|
-
<td>
|
|
99
|
-
<div class="sch-name">${job.displayName || job.name}</div>
|
|
100
|
-
${job.purpose && html`<div class="sch-muted sch-purpose">${job.purpose}</div>`}
|
|
101
|
-
${job.createdByAgent && html`<span class="sch-tag">${t('profile.scheduler.byAgent')}</span>`}
|
|
102
|
-
</td>
|
|
103
|
-
<td>${kindBadge(job.type)}</td>
|
|
104
|
-
<td><code class="sch-cron">${job.cron}</code>${job.timezone && html`<div class="sch-muted">${job.timezone}</div>`}</td>
|
|
105
|
-
<td>${job.agentName || html`<span class="sch-muted">—</span>`}</td>
|
|
106
|
-
<td>${job.lastRunAt ? timeAgo(job.lastRunAt) : html`<span class="sch-muted">${t('profile.scheduler.never')}</span>`} ${resultBadge(job.lastRunResult)}</td>
|
|
107
|
-
<td>${job.nextRunAt ? timeAgo(job.nextRunAt) : html`<span class="sch-muted">—</span>`}</td>
|
|
108
|
-
<td>${job.runCount ?? 0}</td>
|
|
109
|
-
<td class="sch-actions">
|
|
110
|
-
<button class="btn-ghost sch-btn" onClick=${() => onToggle(job)} title=${job.enabled ? t('profile.scheduler.pause') : t('profile.scheduler.resume')}>
|
|
111
|
-
${job.enabled ? t('profile.scheduler.pause') : t('profile.scheduler.resume')}
|
|
112
|
-
</button>
|
|
113
|
-
<button class="btn-ghost sch-btn" onClick=${() => onTrigger(job)}>${t('profile.scheduler.runNow')}</button>
|
|
114
|
-
<button class="btn-danger sch-btn" onClick=${() => onCancel(job)}>${t('profile.scheduler.cancel')}</button>
|
|
115
|
-
</td>
|
|
116
|
-
</tr>`;
|
|
117
|
-
|
|
118
81
|
if (loading) return html`<div class="sch-loading">${t('profile.scheduler.loading')}</div>`;
|
|
119
82
|
|
|
120
83
|
return html`
|
|
@@ -139,16 +102,7 @@ export default function SchedulerTab({ showToast }) {
|
|
|
139
102
|
<h3 class="sch-section-title">${t('profile.scheduler.managedTitle')}</h3>
|
|
140
103
|
${data.managed.length === 0
|
|
141
104
|
? html`<div class="sch-empty">${t('profile.scheduler.noManaged')}</div>`
|
|
142
|
-
: html`<
|
|
143
|
-
<th>${t('profile.scheduler.col.name')}</th>
|
|
144
|
-
<th>${t('profile.scheduler.col.kind')}</th>
|
|
145
|
-
<th>${t('profile.scheduler.col.cron')}</th>
|
|
146
|
-
<th>${t('profile.scheduler.col.agent')}</th>
|
|
147
|
-
<th>${t('profile.scheduler.col.lastRun')}</th>
|
|
148
|
-
<th>${t('profile.scheduler.col.nextRun')}</th>
|
|
149
|
-
<th>${t('profile.scheduler.col.runs')}</th>
|
|
150
|
-
<th></th>
|
|
151
|
-
</tr></thead><tbody>${data.managed.map(renderManagedRow)}</tbody></table>`}
|
|
105
|
+
: html`<div class="sch-card-list">${data.managed.map(j => html`<${ScheduleItem} key=${j.id} schedule=${j} onChanged=${loadData} showToast=${showToast} />`)}</div>`}
|
|
152
106
|
</div>
|
|
153
107
|
|
|
154
108
|
<!-- Owner extension cron -->
|
|
@@ -166,7 +120,7 @@ export default function SchedulerTab({ showToast }) {
|
|
|
166
120
|
<td><code class="sch-cron">${j.extensionName}${j.actionId ? '/' + j.actionId : ''}</code></td>
|
|
167
121
|
<td><code class="sch-cron">${j.cron}</code></td>
|
|
168
122
|
<td>${j.lastRunAt ? timeAgo(j.lastRunAt) : '—'} ${resultBadge(j.lastRunResult)}</td>
|
|
169
|
-
<td>${
|
|
123
|
+
<td>${formatUntil(j.nextRunAt)}</td>
|
|
170
124
|
</tr>`)}
|
|
171
125
|
</tbody></table>
|
|
172
126
|
</div>`}
|
|
@@ -4588,6 +4588,71 @@ export interface paths {
|
|
|
4588
4588
|
patch?: never;
|
|
4589
4589
|
trace?: never;
|
|
4590
4590
|
};
|
|
4591
|
+
"/v1/site/memory-keys": {
|
|
4592
|
+
parameters: {
|
|
4593
|
+
query?: never;
|
|
4594
|
+
header?: never;
|
|
4595
|
+
path?: never;
|
|
4596
|
+
cookie?: never;
|
|
4597
|
+
};
|
|
4598
|
+
/**
|
|
4599
|
+
* List portal memory keys
|
|
4600
|
+
* @description List memory keys under the `portal/*` namespace (site `__site__` owner)
|
|
4601
|
+
* with their values. These resolve in templates via `{{memory:portal/key}}`.
|
|
4602
|
+
* Operator-only (Tier 2).
|
|
4603
|
+
*/
|
|
4604
|
+
get: operations["getSiteMemoryKeys"];
|
|
4605
|
+
put?: never;
|
|
4606
|
+
post?: never;
|
|
4607
|
+
delete?: never;
|
|
4608
|
+
options?: never;
|
|
4609
|
+
head?: never;
|
|
4610
|
+
patch?: never;
|
|
4611
|
+
trace?: never;
|
|
4612
|
+
};
|
|
4613
|
+
"/v1/site/memory": {
|
|
4614
|
+
parameters: {
|
|
4615
|
+
query?: never;
|
|
4616
|
+
header?: never;
|
|
4617
|
+
path?: never;
|
|
4618
|
+
cookie?: never;
|
|
4619
|
+
};
|
|
4620
|
+
get?: never;
|
|
4621
|
+
put?: never;
|
|
4622
|
+
/**
|
|
4623
|
+
* Set a portal memory key
|
|
4624
|
+
* @description Set a single portal memory key in the site (`__site__`) namespace so it
|
|
4625
|
+
* resolves in templates via `{{memory:portal/key}}`. The key must start with
|
|
4626
|
+
* `portal/`. Operator-only (Tier 2). Blocked in load-balancer mode.
|
|
4627
|
+
*/
|
|
4628
|
+
post: operations["setSiteMemory"];
|
|
4629
|
+
delete?: never;
|
|
4630
|
+
options?: never;
|
|
4631
|
+
head?: never;
|
|
4632
|
+
patch?: never;
|
|
4633
|
+
trace?: never;
|
|
4634
|
+
};
|
|
4635
|
+
"/v1/site/memory/{key}": {
|
|
4636
|
+
parameters: {
|
|
4637
|
+
query?: never;
|
|
4638
|
+
header?: never;
|
|
4639
|
+
path?: never;
|
|
4640
|
+
cookie?: never;
|
|
4641
|
+
};
|
|
4642
|
+
get?: never;
|
|
4643
|
+
put?: never;
|
|
4644
|
+
post?: never;
|
|
4645
|
+
/**
|
|
4646
|
+
* Delete a portal memory key
|
|
4647
|
+
* @description Delete a single portal memory key from the site (`__site__`) namespace.
|
|
4648
|
+
* Operator-only (Tier 2). Blocked in load-balancer mode.
|
|
4649
|
+
*/
|
|
4650
|
+
delete: operations["deleteSiteMemory"];
|
|
4651
|
+
options?: never;
|
|
4652
|
+
head?: never;
|
|
4653
|
+
patch?: never;
|
|
4654
|
+
trace?: never;
|
|
4655
|
+
};
|
|
4591
4656
|
"/v1/site/changelog": {
|
|
4592
4657
|
parameters: {
|
|
4593
4658
|
query?: never;
|
|
@@ -4692,7 +4757,18 @@ export interface paths {
|
|
|
4692
4757
|
* No authentication required (Tier 0).
|
|
4693
4758
|
*/
|
|
4694
4759
|
get: operations["getExtension"];
|
|
4695
|
-
|
|
4760
|
+
/**
|
|
4761
|
+
* Upsert (redeploy) an extension
|
|
4762
|
+
* @description Idempotent upsert. Creates the extension if it does not exist, otherwise replaces it in
|
|
4763
|
+
* place WITHOUT a destructive delete: the extension keeps its identity, its `ext:{name}`
|
|
4764
|
+
* namespace memory, and its instances. Action scripts + manifest are swapped atomically, so
|
|
4765
|
+
* the next `/v1/ext/...` call runs the new code and the action endpoint never disappears
|
|
4766
|
+
* mid-redeploy. For an active extension, schedules are re-registered from the new manifest
|
|
4767
|
+
* and `@activate` jobs re-run. Identical bytes are a safe 200 no-op, and updating never
|
|
4768
|
+
* consumes a quota slot. Create requires ext:write (like POST); update requires owning the
|
|
4769
|
+
* extension.
|
|
4770
|
+
*/
|
|
4771
|
+
put: operations["upsertExtension"];
|
|
4696
4772
|
post?: never;
|
|
4697
4773
|
/**
|
|
4698
4774
|
* Uninstall an extension
|
|
@@ -5267,7 +5343,19 @@ export interface paths {
|
|
|
5267
5343
|
};
|
|
5268
5344
|
/** Get cortex model details */
|
|
5269
5345
|
get: operations["getCortexModel"];
|
|
5270
|
-
|
|
5346
|
+
/**
|
|
5347
|
+
* Upsert (redeploy) a cortex model
|
|
5348
|
+
* @description Idempotent upsert. Creates the cortex if it does not exist, otherwise replaces it
|
|
5349
|
+
* in place WITHOUT a delete: the cortex keeps its identity and stays served for the
|
|
5350
|
+
* whole call (lib files are swapped in place, never delete-then-recreated for files
|
|
5351
|
+
* that persist, so GET /v1/cortex/{name}/libs/{libFile} never 404s mid-upsert). For an
|
|
5352
|
+
* active cortex, init is re-run so new behaviour goes live (unlike re-activate, which
|
|
5353
|
+
* skips init when already active). Updating an existing cortex never consumes a quota
|
|
5354
|
+
* slot, and an identical manifest + libs is a safe 200 no-op.
|
|
5355
|
+
* Same scope as POST /v1/cortex (cortex:write); owner/operator bypass scope, and only the
|
|
5356
|
+
* installing owner may update.
|
|
5357
|
+
*/
|
|
5358
|
+
put: operations["upsertCortexModel"];
|
|
5271
5359
|
post?: never;
|
|
5272
5360
|
/** Delete cortex model */
|
|
5273
5361
|
delete: operations["deleteCortexModel"];
|
|
@@ -17932,6 +18020,114 @@ export interface operations {
|
|
|
17932
18020
|
422: components["responses"]["UnprocessableEntity"];
|
|
17933
18021
|
};
|
|
17934
18022
|
};
|
|
18023
|
+
getSiteMemoryKeys: {
|
|
18024
|
+
parameters: {
|
|
18025
|
+
query?: never;
|
|
18026
|
+
header?: never;
|
|
18027
|
+
path?: never;
|
|
18028
|
+
cookie?: never;
|
|
18029
|
+
};
|
|
18030
|
+
requestBody?: never;
|
|
18031
|
+
responses: {
|
|
18032
|
+
/** @description Portal memory keys */
|
|
18033
|
+
200: {
|
|
18034
|
+
headers: {
|
|
18035
|
+
[name: string]: unknown;
|
|
18036
|
+
};
|
|
18037
|
+
content: {
|
|
18038
|
+
"application/json": components["schemas"]["AimeatEnvelope"] & {
|
|
18039
|
+
data?: {
|
|
18040
|
+
keys?: {
|
|
18041
|
+
key?: string;
|
|
18042
|
+
value?: unknown;
|
|
18043
|
+
}[];
|
|
18044
|
+
};
|
|
18045
|
+
};
|
|
18046
|
+
};
|
|
18047
|
+
};
|
|
18048
|
+
};
|
|
18049
|
+
};
|
|
18050
|
+
setSiteMemory: {
|
|
18051
|
+
parameters: {
|
|
18052
|
+
query?: never;
|
|
18053
|
+
header?: never;
|
|
18054
|
+
path?: never;
|
|
18055
|
+
cookie?: never;
|
|
18056
|
+
};
|
|
18057
|
+
requestBody: {
|
|
18058
|
+
content: {
|
|
18059
|
+
"application/json": {
|
|
18060
|
+
/**
|
|
18061
|
+
* @description Memory key, must start with `portal/`
|
|
18062
|
+
* @example portal/about
|
|
18063
|
+
*/
|
|
18064
|
+
key: string;
|
|
18065
|
+
/** @description Value (may contain operator-trusted HTML) */
|
|
18066
|
+
value: string;
|
|
18067
|
+
};
|
|
18068
|
+
};
|
|
18069
|
+
};
|
|
18070
|
+
responses: {
|
|
18071
|
+
/** @description Memory key stored */
|
|
18072
|
+
200: {
|
|
18073
|
+
headers: {
|
|
18074
|
+
[name: string]: unknown;
|
|
18075
|
+
};
|
|
18076
|
+
content: {
|
|
18077
|
+
"application/json": components["schemas"]["AimeatEnvelope"] & {
|
|
18078
|
+
data?: {
|
|
18079
|
+
stored?: boolean;
|
|
18080
|
+
key?: string;
|
|
18081
|
+
};
|
|
18082
|
+
};
|
|
18083
|
+
};
|
|
18084
|
+
};
|
|
18085
|
+
/** @description Portal is read-only (load-balancer mode) */
|
|
18086
|
+
409: {
|
|
18087
|
+
headers: {
|
|
18088
|
+
[name: string]: unknown;
|
|
18089
|
+
};
|
|
18090
|
+
content?: never;
|
|
18091
|
+
};
|
|
18092
|
+
422: components["responses"]["UnprocessableEntity"];
|
|
18093
|
+
};
|
|
18094
|
+
};
|
|
18095
|
+
deleteSiteMemory: {
|
|
18096
|
+
parameters: {
|
|
18097
|
+
query?: never;
|
|
18098
|
+
header?: never;
|
|
18099
|
+
path: {
|
|
18100
|
+
/** @description URL-encoded portal memory key (e.g. `portal%2Fabout`) */
|
|
18101
|
+
key: string;
|
|
18102
|
+
};
|
|
18103
|
+
cookie?: never;
|
|
18104
|
+
};
|
|
18105
|
+
requestBody?: never;
|
|
18106
|
+
responses: {
|
|
18107
|
+
/** @description Memory key deleted */
|
|
18108
|
+
200: {
|
|
18109
|
+
headers: {
|
|
18110
|
+
[name: string]: unknown;
|
|
18111
|
+
};
|
|
18112
|
+
content: {
|
|
18113
|
+
"application/json": components["schemas"]["AimeatEnvelope"] & {
|
|
18114
|
+
data?: {
|
|
18115
|
+
deleted?: boolean;
|
|
18116
|
+
key?: string;
|
|
18117
|
+
};
|
|
18118
|
+
};
|
|
18119
|
+
};
|
|
18120
|
+
};
|
|
18121
|
+
404: components["responses"]["NotFound"];
|
|
18122
|
+
/** @description Portal is read-only (load-balancer mode) */
|
|
18123
|
+
409: {
|
|
18124
|
+
headers: {
|
|
18125
|
+
[name: string]: unknown;
|
|
18126
|
+
};
|
|
18127
|
+
content?: never;
|
|
18128
|
+
};
|
|
18129
|
+
};
|
|
18130
|
+
};
|
|
17935
18131
|
getSiteChangelog: {
|
|
17936
18132
|
parameters: {
|
|
17937
18133
|
query?: {
|
|
@@ -18109,6 +18305,50 @@ export interface operations {
|
|
|
18109
18305
|
404: components["responses"]["NotFound"];
|
|
18110
18306
|
};
|
|
18111
18307
|
};
|
|
18308
|
+
upsertExtension: {
|
|
18309
|
+
parameters: {
|
|
18310
|
+
query?: never;
|
|
18311
|
+
header?: never;
|
|
18312
|
+
path: {
|
|
18313
|
+
/** @description Extension name (must match metadata.name in the manifest) */
|
|
18314
|
+
name: string;
|
|
18315
|
+
};
|
|
18316
|
+
cookie?: never;
|
|
18317
|
+
};
|
|
18318
|
+
requestBody: {
|
|
18319
|
+
content: {
|
|
18320
|
+
"application/json": {
|
|
18321
|
+
/** @description Extension manifest as a YAML string. */
|
|
18322
|
+
manifest: string;
|
|
18323
|
+
/** @description Map of script filename to JavaScript source. */
|
|
18324
|
+
scripts: {
|
|
18325
|
+
[key: string]: string;
|
|
18326
|
+
};
|
|
18327
|
+
};
|
|
18328
|
+
};
|
|
18329
|
+
};
|
|
18330
|
+
responses: {
|
|
18331
|
+
200: components["responses"]["AimeatSuccess"];
|
|
18332
|
+
201: components["responses"]["AimeatSuccess"];
|
|
18333
|
+
400: components["responses"]["BadRequest"];
|
|
18334
|
+
401: components["responses"]["Unauthorized"];
|
|
18335
|
+
403: components["responses"]["Forbidden"];
|
|
18336
|
+
/** @description Install limit exceeded. */
|
|
18337
|
+
409: {
|
|
18338
|
+
headers: {
|
|
18339
|
+
[name: string]: unknown;
|
|
18340
|
+
};
|
|
18341
|
+
content?: never;
|
|
18342
|
+
};
|
|
18343
|
+
/** @description Per-owner extension limit exceeded. */
|
|
18344
|
+
429: {
|
|
18345
|
+
headers: {
|
|
18346
|
+
[name: string]: unknown;
|
|
18347
|
+
};
|
|
18348
|
+
content?: never;
|
|
18349
|
+
};
|
|
18350
|
+
};
|
|
18351
|
+
};
|
|
18112
18352
|
uninstallExtension: {
|
|
18113
18353
|
parameters: {
|
|
18114
18354
|
query?: never;
|
|
@@ -18760,6 +19000,41 @@ export interface operations {
|
|
|
18760
19000
|
404: components["responses"]["NotFound"];
|
|
18761
19001
|
};
|
|
18762
19002
|
};
|
|
19003
|
+
upsertCortexModel: {
|
|
19004
|
+
parameters: {
|
|
19005
|
+
query?: never;
|
|
19006
|
+
header?: never;
|
|
19007
|
+
path: {
|
|
19008
|
+
name: string;
|
|
19009
|
+
};
|
|
19010
|
+
cookie?: never;
|
|
19011
|
+
};
|
|
19012
|
+
requestBody: {
|
|
19013
|
+
content: {
|
|
19014
|
+
"application/json": {
|
|
19015
|
+
/** @description Cortex manifest as a YAML string. metadata.name must match the URL name. */
|
|
19016
|
+
manifest: string;
|
|
19017
|
+
/** @description Map of lib filename to JavaScript source. */
|
|
19018
|
+
libs?: {
|
|
19019
|
+
[key: string]: string;
|
|
19020
|
+
};
|
|
19021
|
+
};
|
|
19022
|
+
};
|
|
19023
|
+
};
|
|
19024
|
+
responses: {
|
|
19025
|
+
200: components["responses"]["AimeatSuccess"];
|
|
19026
|
+
201: components["responses"]["AimeatSuccess"];
|
|
19027
|
+
400: components["responses"]["BadRequest"];
|
|
19028
|
+
403: components["responses"]["Forbidden"];
|
|
19029
|
+
/** @description Manifest, lib, or quota limit exceeded. */
|
|
19030
|
+
413: {
|
|
19031
|
+
headers: {
|
|
19032
|
+
[name: string]: unknown;
|
|
19033
|
+
};
|
|
19034
|
+
content?: never;
|
|
19035
|
+
};
|
|
19036
|
+
};
|
|
19037
|
+
};
|
|
18763
19038
|
deleteCortexModel: {
|
|
18764
19039
|
parameters: {
|
|
18765
19040
|
query?: never;
|