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
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* @version-history
|
|
8
8
|
* v1.1.0 — 2026-06-02 — Admin design unification: raw template textarea →
|
|
9
9
|
* adm-textarea adm-input-full (drop inline mono/border/background styles).
|
|
10
|
+
* v1.2.0 — 2026-06-03 — Active-source status (custom template vs built-in SPA)
|
|
11
|
+
* + "Load current page" button that seeds the editor with the live / HTML.
|
|
12
|
+
* v1.3.0 — 2026-06-03 — Fix portal memory keys writing to the wrong namespace
|
|
13
|
+
* (now /v1/site/memory → __site__ so {{memory:portal/*}} resolves); add AI
|
|
14
|
+
* bundle import (/v1/site/import) so the AI-Assisted result no longer 422s in
|
|
15
|
+
* the template box; reload the preview iframe after every change + Clear Cache.
|
|
10
16
|
*/
|
|
11
17
|
import { h } from 'preact';
|
|
12
18
|
import { useState, useEffect } from 'preact/hooks';
|
|
@@ -17,7 +23,8 @@ import { escHtml, copyToClipboard } from '/js/utils.js';
|
|
|
17
23
|
import { dt, Badge, ExpandableHelp, useToast, Toast } from './shared.js';
|
|
18
24
|
import {
|
|
19
25
|
saveSiteTemplate, deleteSiteTemplate, clearSiteCache,
|
|
20
|
-
getSiteMemoryKeys, getSitePrompt,
|
|
26
|
+
getSiteMemoryKeys, getSitePrompt, setSiteMemory, deleteSiteMemory,
|
|
27
|
+
importSiteBundle, triggerLbSync,
|
|
21
28
|
} from '/js/services/admin.js';
|
|
22
29
|
import { useConfirm } from '/components/Modal.js';
|
|
23
30
|
|
|
@@ -28,12 +35,17 @@ export default function PortalTab({ data, reload }) {
|
|
|
28
35
|
const tmpl = p.template || {};
|
|
29
36
|
const changes = (p.changelog?.entries) || [];
|
|
30
37
|
const isLb = meta.lb_mode?.enabled;
|
|
38
|
+
const hasCustom = !!meta.has_custom_template;
|
|
31
39
|
|
|
32
40
|
const { confirm, ConfirmUI } = useConfirm();
|
|
33
41
|
const [template, setTemplate] = useState(tmpl.template || '');
|
|
34
42
|
const [memKeys, setMemKeys] = useState(null);
|
|
35
43
|
const [newKey, setNewKey] = useState('');
|
|
36
44
|
const [newVal, setNewVal] = useState('');
|
|
45
|
+
const [aiBundle, setAiBundle] = useState('');
|
|
46
|
+
// Bumped after any change so the preview iframe re-fetches `/` (busts browser cache).
|
|
47
|
+
const [previewNonce, setPreviewNonce] = useState(0);
|
|
48
|
+
const bumpPreview = () => setPreviewNonce(n => n + 1);
|
|
37
49
|
|
|
38
50
|
useEffect(() => { loadMemKeys(); }, []);
|
|
39
51
|
|
|
@@ -45,10 +57,56 @@ export default function PortalTab({ data, reload }) {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
async function saveTemplate() {
|
|
48
|
-
|
|
60
|
+
// The AI-Assisted prompt produces a JSON import bundle, not raw HTML. If the
|
|
61
|
+
// user pasted that here, the template route would 422 — point them to Import.
|
|
62
|
+
if (template.trimStart().startsWith('{')) {
|
|
63
|
+
showErr(t('dashboard.portalSaveLooksLikeJson'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try { await saveSiteTemplate(template); bumpPreview(); reload(); }
|
|
49
67
|
catch (e) { showErr(e.message); }
|
|
50
68
|
}
|
|
51
69
|
|
|
70
|
+
async function importAiBundle() {
|
|
71
|
+
let bundle;
|
|
72
|
+
try { bundle = JSON.parse(aiBundle); }
|
|
73
|
+
catch { showErr(t('dashboard.portalImportInvalid')); return; }
|
|
74
|
+
if (!bundle || typeof bundle !== 'object' || (!bundle.template && !bundle.memory && !bundle.kv)) {
|
|
75
|
+
showErr(t('dashboard.portalImportInvalid'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const res = await importSiteBundle(bundle);
|
|
80
|
+
const d = res.data || {};
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (d.template_stored) parts.push('template');
|
|
83
|
+
if (d.memory_keys_written) parts.push(d.memory_keys_written + ' memory');
|
|
84
|
+
if (d.kv_pairs_updated) parts.push(d.kv_pairs_updated + ' KV');
|
|
85
|
+
showOk(t('dashboard.portalImportDone').replace('{summary}', parts.join(', ') || '—'));
|
|
86
|
+
if (typeof bundle.template === 'string') setTemplate(bundle.template);
|
|
87
|
+
setAiBundle('');
|
|
88
|
+
loadMemKeys();
|
|
89
|
+
bumpPreview();
|
|
90
|
+
reload();
|
|
91
|
+
} catch (e) { showErr(e.message); }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function loadCurrentAsTemplate() {
|
|
95
|
+
const run = async () => {
|
|
96
|
+
try {
|
|
97
|
+
const resp = await fetch('/', { headers: { Accept: 'text/html' }, cache: 'no-store' });
|
|
98
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
99
|
+
setTemplate(await resp.text());
|
|
100
|
+
showOk(t('dashboard.portalLoadedCurrent'));
|
|
101
|
+
} catch (e) { showErr(e.message); }
|
|
102
|
+
};
|
|
103
|
+
if (template && template.trim()) {
|
|
104
|
+
confirm(t('dashboard.portalLoadCurrentConfirm'), run);
|
|
105
|
+
} else {
|
|
106
|
+
run();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
52
110
|
function downloadTemplate() {
|
|
53
111
|
if (!template) return;
|
|
54
112
|
const blob = new Blob([template], { type: 'text/html' });
|
|
@@ -58,24 +116,28 @@ export default function PortalTab({ data, reload }) {
|
|
|
58
116
|
|
|
59
117
|
function resetTemplate() {
|
|
60
118
|
confirm(t('dashboard.portalResetDefault') + '?', async () => {
|
|
61
|
-
try { await deleteSiteTemplate(); reload(); }
|
|
119
|
+
try { await deleteSiteTemplate(); setTemplate(''); bumpPreview(); reload(); }
|
|
62
120
|
catch (e) { showErr(e.message); }
|
|
63
121
|
}, { danger: true });
|
|
64
122
|
}
|
|
65
123
|
|
|
66
124
|
async function doClearCache() {
|
|
67
|
-
try { await clearSiteCache(); } catch (e) { showErr(e.message); }
|
|
125
|
+
try { await clearSiteCache(); bumpPreview(); } catch (e) { showErr(e.message); }
|
|
68
126
|
}
|
|
69
127
|
|
|
70
128
|
async function addMem() {
|
|
71
129
|
if (!newKey.trim()) return;
|
|
72
|
-
try {
|
|
73
|
-
|
|
130
|
+
try {
|
|
131
|
+
await setSiteMemory(newKey.trim(), newVal);
|
|
132
|
+
setNewKey(''); setNewVal('');
|
|
133
|
+
showOk(t('dashboard.portalMemorySaved'));
|
|
134
|
+
loadMemKeys(); bumpPreview();
|
|
135
|
+
} catch (e) { showErr(e.message); }
|
|
74
136
|
}
|
|
75
137
|
|
|
76
138
|
function delMem(key) {
|
|
77
139
|
confirm(t('dashboard.portalDeleteKeyConfirm').replace('{key}', key), async () => {
|
|
78
|
-
try { await
|
|
140
|
+
try { await deleteSiteMemory(key); loadMemKeys(); bumpPreview(); }
|
|
79
141
|
catch (e) { showErr(e.message); }
|
|
80
142
|
}, { danger: true });
|
|
81
143
|
}
|
|
@@ -120,7 +182,7 @@ export default function PortalTab({ data, reload }) {
|
|
|
120
182
|
<!-- Preview -->
|
|
121
183
|
<div class="adm-card">
|
|
122
184
|
<h3>${t('dashboard.portalPreview')}</h3>
|
|
123
|
-
<iframe src=
|
|
185
|
+
<iframe src=${'/?_preview=' + previewNonce} style="width:100%;height:400px;border:1px solid var(--glass-border);border-radius:8px;background:#fff" sandbox="allow-same-origin allow-scripts"></iframe>
|
|
124
186
|
<div class="adm-flex adm-mt-sm">
|
|
125
187
|
<button class="adm-btn-action" onClick=${doClearCache}>\u{1F6AB} ${t('dashboard.portalClearCache')}</button>
|
|
126
188
|
</div>
|
|
@@ -129,6 +191,22 @@ export default function PortalTab({ data, reload }) {
|
|
|
129
191
|
<!-- Template editor -->
|
|
130
192
|
<div class="adm-card">
|
|
131
193
|
<h3>${t('dashboard.portalTemplate')}</h3>
|
|
194
|
+
<div class="adm-erow">
|
|
195
|
+
<span class="adm-elabel">${t('dashboard.portalActiveSource')}</span>
|
|
196
|
+
<span class="adm-eval">
|
|
197
|
+
${hasCustom
|
|
198
|
+
? html`<span class="adm-badge adm-badge-active">${t('dashboard.portalCustomActive')}</span>${tmpl.updated_at ? html` · ${dt(tmpl.updated_at)}` : ''}`
|
|
199
|
+
: html`<span class="adm-badge adm-badge-info">${t('dashboard.portalDefaultActive')}</span>`}
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
<p class="adm-text-dim adm-text-base adm-mb-sm">
|
|
203
|
+
${hasCustom
|
|
204
|
+
? t('dashboard.portalCustomExplain')
|
|
205
|
+
: t('dashboard.portalDefaultExplain').replace('{action}', t('dashboard.portalLoadCurrent'))}
|
|
206
|
+
</p>
|
|
207
|
+
<div class="adm-mb-sm">
|
|
208
|
+
<button class="adm-btn-action" onClick=${loadCurrentAsTemplate}>\u{1F4C4} ${t('dashboard.portalLoadCurrent')}</button>
|
|
209
|
+
</div>
|
|
132
210
|
<${ExpandableHelp} title=${t('dashboard.portalTagHelpTitle')}>
|
|
133
211
|
<p>${t('dashboard.portalTagHelpDetail')}</p>
|
|
134
212
|
<table>
|
|
@@ -194,9 +272,16 @@ export default function PortalTab({ data, reload }) {
|
|
|
194
272
|
<div class="adm-card">
|
|
195
273
|
<h3>${t('dashboard.portalAiChat')}</h3>
|
|
196
274
|
<p class="adm-text-dim adm-text-base">${t('dashboard.portalAiExplain')}</p>
|
|
197
|
-
<div class="adm-mt-sm">
|
|
275
|
+
<div class="adm-mt-sm adm-mb-sm">
|
|
198
276
|
<button class="adm-btn-action" onClick=${copyPrompt}>\u{1F4CB} ${t('dashboard.portalAiLoadPrompt')}</button>
|
|
199
277
|
</div>
|
|
278
|
+
<p class="adm-text-dim adm-text-base adm-mb-sm">${t('dashboard.portalAiBundleExplain')}</p>
|
|
279
|
+
<textarea class="adm-textarea adm-input-full" rows="6" value=${aiBundle}
|
|
280
|
+
onInput=${e => setAiBundle(e.target.value)}
|
|
281
|
+
placeholder=${t('dashboard.portalAiBundlePlaceholder')} style="font-size:13px"></textarea>
|
|
282
|
+
<div class="adm-flex adm-mt-sm">
|
|
283
|
+
<button class="adm-btn-action" onClick=${importAiBundle}>\u{1F4E5} ${t('dashboard.portalAiImport')}</button>
|
|
284
|
+
</div>
|
|
200
285
|
</div>
|
|
201
286
|
|
|
202
287
|
<!-- Changelog -->
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* @description Agent card component with collapsed/expanded states,
|
|
4
4
|
* Two-Zone Header (identity + state-dependent status), and tab bar.
|
|
5
5
|
* @version-history
|
|
6
|
+
* v1.13.0 -- 2026-06-03 -- Deep-link props: preSelectedTab forces a tab (and
|
|
7
|
+
* locks it past the async README default), openTaskId + openTaskNonce are
|
|
8
|
+
* threaded to the Tasks sub-tab so the fleet "running now" panel can open a
|
|
9
|
+
* specific task directly.
|
|
6
10
|
* v1.12.0 -- 2026-06-02 -- Active tracked tab never shows its own badge and is
|
|
7
11
|
* kept marked-seen as new items arrive (so a message landing while you're on
|
|
8
12
|
* the Messages tab doesn't raise a (1) you're already looking at).
|
|
@@ -111,7 +115,7 @@ function totalChanges(changes) {
|
|
|
111
115
|
return (changes.tasks || 0) + (changes.messages || 0) + (changes.memory || 0);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, changes, onTabSeen, onScopesClick, onDeleteClick, onFederateToggle, onPopOut, soloMode = false }) {
|
|
118
|
+
export default function AgentCard({ agent, onboarding, expanded, onToggle, session, showToast, allAgents, changes, onTabSeen, onScopesClick, onDeleteClick, onFederateToggle, onPopOut, soloMode = false, preSelectedTab = null, openTaskId = null, openTaskNonce = 0 }) {
|
|
115
119
|
const state = detectAgentState(agent, onboarding);
|
|
116
120
|
const [activeTab, setActiveTab] = useState(null);
|
|
117
121
|
// README markdown: undefined = not loaded, '' = none published, string = show tab.
|
|
@@ -119,6 +123,9 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
119
123
|
// True once the user clicks a tab, so the async README default never yanks
|
|
120
124
|
// them off a tab they chose.
|
|
121
125
|
const userPickedTab = useRef(false);
|
|
126
|
+
// Last deep-link nonce we acted on, so a fresh panel click re-forces the tab
|
|
127
|
+
// but a plain re-expand (same nonce) does not override the README default.
|
|
128
|
+
const lastDeepLinkNonce = useRef(0);
|
|
122
129
|
|
|
123
130
|
// Load the README once the card is expanded (lazy — collapsed cards skip it).
|
|
124
131
|
useEffect(() => {
|
|
@@ -141,6 +148,19 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
141
148
|
setActiveTab(hasReadme ? 'readme' : getDefaultTab(state));
|
|
142
149
|
}, [expanded, readme]);
|
|
143
150
|
|
|
151
|
+
// Deep-link: when the fleet "running now" panel requests this agent's Tasks
|
|
152
|
+
// tab (preSelectedTab), force that tab and lock it (userPickedTab) so the
|
|
153
|
+
// async README default can't yank it away. Gated on a NEW openTaskNonce so a
|
|
154
|
+
// fresh panel click re-forces it, but a plain manual re-expand (same nonce)
|
|
155
|
+
// leaves the README default / the user's own tab choice alone.
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!expanded || !preSelectedTab || !openTaskNonce) return;
|
|
158
|
+
if (openTaskNonce === lastDeepLinkNonce.current) return;
|
|
159
|
+
lastDeepLinkNonce.current = openTaskNonce;
|
|
160
|
+
userPickedTab.current = true;
|
|
161
|
+
setActiveTab(preSelectedTab);
|
|
162
|
+
}, [expanded, preSelectedTab, openTaskNonce]);
|
|
163
|
+
|
|
144
164
|
// While the owner is actively viewing a tracked tab (Tasks/Messages/Memory),
|
|
145
165
|
// keep it marked-seen as new items arrive. Without this, a message landing
|
|
146
166
|
// while you're on the Messages tab would raise its (1) badge even though you
|
|
@@ -256,7 +276,7 @@ export default function AgentCard({ agent, onboarding, expanded, onToggle, sessi
|
|
|
256
276
|
|
|
257
277
|
<!-- Tab Content -->
|
|
258
278
|
<div class="pf-agd-tab-content" onClick=${(e) => e.stopPropagation()}>
|
|
259
|
-
${renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme)}
|
|
279
|
+
${renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce)}
|
|
260
280
|
</div>
|
|
261
281
|
|
|
262
282
|
<!-- Card Footer: Scopes + Delete -->
|
|
@@ -550,12 +570,12 @@ function ProblemZone2({ agent, setActiveTab, showToast }) {
|
|
|
550
570
|
`;
|
|
551
571
|
}
|
|
552
572
|
|
|
553
|
-
function renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme) {
|
|
573
|
+
function renderTabContent(activeTab, agent, onboarding, session, showToast, allAgents, readme, openTaskId, openTaskNonce) {
|
|
554
574
|
const props = { agent, onboarding, session, showToast, agentName: agent.name, allAgents };
|
|
555
575
|
switch (activeTab) {
|
|
556
576
|
case 'readme': return html`<${TabReadme} readme=${readme} />`;
|
|
557
577
|
case 'integration': return html`<${TabIntegration} ...${props} />`;
|
|
558
|
-
case 'tasks': return html`<${TabTasks} ...${props} />`;
|
|
578
|
+
case 'tasks': return html`<${TabTasks} ...${props} openTaskId=${openTaskId} openTaskNonce=${openTaskNonce} />`;
|
|
559
579
|
case 'messages': return html`<${TabMessages} ...${props} />`;
|
|
560
580
|
case 'data-access': return html`<${TabDataAccess} ...${props} />`;
|
|
561
581
|
case 'directives': return html`<${TabDirectives} ...${props} />`;
|
|
@@ -11,14 +11,12 @@ import { h } from 'preact';
|
|
|
11
11
|
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
|
|
12
12
|
import htm from 'htm';
|
|
13
13
|
import { t } from '/js/i18n.js';
|
|
14
|
-
import {
|
|
15
|
-
import { listAgentSchedules, setScheduleEnabled, triggerSchedule, deleteSchedule } from '/js/services/schedules.js';
|
|
14
|
+
import { listAgentSchedules } from '/js/services/schedules.js';
|
|
16
15
|
import { CreateForm } from '../scheduler-tab.js';
|
|
16
|
+
import ScheduleItem from '../schedule-item.js';
|
|
17
17
|
|
|
18
18
|
const html = htm.bind(h);
|
|
19
19
|
|
|
20
|
-
const KIND_LABEL = { ai: 'profile.scheduler.kind.ai', agent_task: 'profile.scheduler.kind.agent_task', extension: 'profile.scheduler.kind.extension', core: 'profile.scheduler.kind.core' };
|
|
21
|
-
|
|
22
20
|
export default function TabSchedules({ agentName, allAgents = [], showToast }) {
|
|
23
21
|
const [managed, setManaged] = useState([]);
|
|
24
22
|
const [internal, setInternal] = useState([]);
|
|
@@ -45,13 +43,6 @@ export default function TabSchedules({ agentName, allAgents = [], showToast }) {
|
|
|
45
43
|
return () => { window.removeEventListener('aimeat-live-update', handler); clearInterval(poller); };
|
|
46
44
|
}, [loadData]);
|
|
47
45
|
|
|
48
|
-
const onToggle = async (j) => { try { await setScheduleEnabled(j.id, !j.enabled); await loadData(); } catch (e) { showToast?.(e.message, true); } };
|
|
49
|
-
const onTrigger = async (j) => { try { await triggerSchedule(j.id); showToast?.(t('profile.scheduler.triggered')); await loadData(); } catch (e) { showToast?.(e.message, true); } };
|
|
50
|
-
const onCancel = async (j) => {
|
|
51
|
-
if (!window.confirm(t('profile.scheduler.confirmCancel'))) return;
|
|
52
|
-
try { await deleteSchedule(j.id); showToast?.(t('profile.scheduler.cancelled')); await loadData(); } catch (e) { showToast?.(e.message, true); }
|
|
53
|
-
};
|
|
54
|
-
|
|
55
46
|
if (loading) return html`<div class="sch-loading">${t('profile.scheduler.loading')}</div>`;
|
|
56
47
|
|
|
57
48
|
return html`
|
|
@@ -70,27 +61,7 @@ export default function TabSchedules({ agentName, allAgents = [], showToast }) {
|
|
|
70
61
|
<h3 class="sch-section-title">${t('profile.scheduler.dispatchedTitle')}</h3>
|
|
71
62
|
${managed.length === 0
|
|
72
63
|
? html`<div class="sch-empty">${t('profile.scheduler.noDispatched')}</div>`
|
|
73
|
-
: html`<
|
|
74
|
-
<th>${t('profile.scheduler.col.name')}</th>
|
|
75
|
-
<th>${t('profile.scheduler.col.kind')}</th>
|
|
76
|
-
<th>${t('profile.scheduler.col.cron')}</th>
|
|
77
|
-
<th>${t('profile.scheduler.col.lastRun')}</th>
|
|
78
|
-
<th>${t('profile.scheduler.col.nextRun')}</th>
|
|
79
|
-
<th></th>
|
|
80
|
-
</tr></thead><tbody>
|
|
81
|
-
${managed.map(j => html`<tr key=${j.id}>
|
|
82
|
-
<td><div class="sch-name">${j.displayName || j.name}</div>${j.createdByAgent && html`<span class="sch-tag">${t('profile.scheduler.byAgent')}</span>`}</td>
|
|
83
|
-
<td><span class="sch-badge sch-badge--${j.type === 'ai' ? 'ai' : j.type === 'extension' ? 'ext' : 'agent'}">${t(KIND_LABEL[j.type] || KIND_LABEL.core)}</span></td>
|
|
84
|
-
<td><code class="sch-cron">${j.cron}</code>${j.timezone && html`<div class="sch-muted">${j.timezone}</div>`}</td>
|
|
85
|
-
<td>${j.lastRunAt ? timeAgo(j.lastRunAt) : html`<span class="sch-muted">${t('profile.scheduler.never')}</span>`} ${j.lastRunResult ? html`<span class="sch-badge ${j.lastRunResult === 'error' ? 'sch-badge--err' : 'sch-badge--ok'}">${j.lastRunResult}</span>` : ''}</td>
|
|
86
|
-
<td>${j.nextRunAt ? timeAgo(j.nextRunAt) : html`<span class="sch-muted">—</span>`}</td>
|
|
87
|
-
<td class="sch-actions">
|
|
88
|
-
<button class="btn-ghost sch-btn" onClick=${() => onToggle(j)}>${j.enabled ? t('profile.scheduler.pause') : t('profile.scheduler.resume')}</button>
|
|
89
|
-
<button class="btn-ghost sch-btn" onClick=${() => onTrigger(j)}>${t('profile.scheduler.runNow')}</button>
|
|
90
|
-
<button class="btn-danger sch-btn" onClick=${() => onCancel(j)}>${t('profile.scheduler.cancel')}</button>
|
|
91
|
-
</td>
|
|
92
|
-
</tr>`)}
|
|
93
|
-
</tbody></table>`}
|
|
64
|
+
: html`<div class="sch-card-list">${managed.map(j => html`<${ScheduleItem} key=${j.id} schedule=${j} onChanged=${loadData} showToast=${showToast} />`)}</div>`}
|
|
94
65
|
</div>
|
|
95
66
|
|
|
96
67
|
<div class="sch-section">
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @description Tasks tab wrapper for agent detail tab-view.
|
|
4
4
|
* Delegates to the existing AgentTasksSubtab component.
|
|
5
5
|
* @version-history
|
|
6
|
+
* v1.2.0 -- 2026-06-03 -- Forward openTaskId + openTaskNonce so the fleet
|
|
7
|
+
* "running now" panel can deep-link straight to a specific task.
|
|
6
8
|
* v1.1.0 -- 2026-06-01 -- Forward the `agent` object so the subtab can show the
|
|
7
9
|
* max_concurrent_tasks runner config.
|
|
8
10
|
* v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
|
|
@@ -14,6 +16,7 @@ import AgentTasksSubtab from '../agents-tasks-subtab.js';
|
|
|
14
16
|
|
|
15
17
|
const html = htm.bind(h);
|
|
16
18
|
|
|
17
|
-
export default function TabTasks({ agent, agentName, session, showToast }) {
|
|
18
|
-
return html`<${AgentTasksSubtab} agent=${agent} agentName=${agentName} session=${session} showToast=${showToast}
|
|
19
|
+
export default function TabTasks({ agent, agentName, session, showToast, openTaskId, openTaskNonce }) {
|
|
20
|
+
return html`<${AgentTasksSubtab} agent=${agent} agentName=${agentName} session=${session} showToast=${showToast}
|
|
21
|
+
openTaskId=${openTaskId} openTaskNonce=${openTaskNonce} />`;
|
|
19
22
|
}
|
|
@@ -35,13 +35,18 @@
|
|
|
35
35
|
* (connect command, MCP onboarding, manual agent prompt, task-runner prompt) with the
|
|
36
36
|
* canonical <CopyButton> (className="copy-prompt-btn" preserves appearance); removed the
|
|
37
37
|
* now-dead copiedAction state + markCopied helper.
|
|
38
|
+
* v3.6.0 -- 2026-06-03 -- Fleet "running now" panel between the agent board and
|
|
39
|
+
* the list: every agent's currently-active tasks in one list (reuses the
|
|
40
|
+
* active tasks already fetched in loadData, so no extra requests); clicking a
|
|
41
|
+
* row deep-links into that agent's Tasks tab and auto-opens the task
|
|
42
|
+
* (expandedAgent + deepLink → AgentCard preSelectedTab/openTaskId).
|
|
38
43
|
*/
|
|
39
44
|
import { h } from 'preact';
|
|
40
45
|
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
41
46
|
import htm from 'htm';
|
|
42
47
|
const html = htm.bind(h);
|
|
43
48
|
import { t } from '/js/i18n.js';
|
|
44
|
-
import { escHtml } from '/js/utils.js';
|
|
49
|
+
import { escHtml, timeAgo } from '/js/utils.js';
|
|
45
50
|
import { CopyButton } from '/components/CopyButton.js';
|
|
46
51
|
import { Spinner } from './shared.js';
|
|
47
52
|
import { apiGet, apiPost, apiPatch } from '/js/api.js';
|
|
@@ -449,6 +454,13 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
449
454
|
const [taskRunnerExpanded, setTaskRunnerExpanded] = useState(false);
|
|
450
455
|
const [taskRunnerName, setTaskRunnerName] = useState('');
|
|
451
456
|
const [taskStatsMap, setTaskStatsMap] = useState({});
|
|
457
|
+
// Currently-active tasks per agent { name: Task[] } — powers the fleet
|
|
458
|
+
// "running now" panel. Reuses the active list already fetched in loadData().
|
|
459
|
+
const [activeTasksMap, setActiveTasksMap] = useState({});
|
|
460
|
+
// Deep-link request from the running-now panel: { agent, taskId, nonce }.
|
|
461
|
+
// The nonce makes a repeat click re-fire the open even for the same task.
|
|
462
|
+
const [deepLink, setDeepLink] = useState(null);
|
|
463
|
+
const deepLinkNonce = useRef(0);
|
|
452
464
|
// Per-agent unseen-change counts { name: { tasks, messages, memory } } driving
|
|
453
465
|
// the collapsed mini-badge + per-tab number badges. Computed in loadData()
|
|
454
466
|
// from the localStorage "last seen per tab" baseline (see loadSeen/countNewer).
|
|
@@ -534,6 +546,7 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
534
546
|
}));
|
|
535
547
|
setOnboardings(obMap);
|
|
536
548
|
const tsMap = {};
|
|
549
|
+
const atsMap = {};
|
|
537
550
|
const seen = loadSeen(session.owner);
|
|
538
551
|
let seenSeeded = false;
|
|
539
552
|
const chMap = {};
|
|
@@ -550,6 +563,7 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
550
563
|
const activeTasks = activeResp?.data?.tasks || [];
|
|
551
564
|
const doneToday = doneTasks.filter(tk => tk.completedAt?.startsWith(today)).length;
|
|
552
565
|
tsMap[a.name] = { done: doneToday, active: activeTasks.length };
|
|
566
|
+
atsMap[a.name] = activeTasks;
|
|
553
567
|
|
|
554
568
|
// Per-tab unseen-change counts. First observation of a tab seeds its
|
|
555
569
|
// baseline to "now" (count 0) so an agent's whole history is never
|
|
@@ -568,9 +582,10 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
568
582
|
else ch[tab] = countNewer(items, agentSeen[tab], fields);
|
|
569
583
|
}
|
|
570
584
|
chMap[a.name] = ch;
|
|
571
|
-
} catch { tsMap[a.name] = null; chMap[a.name] = null; }
|
|
585
|
+
} catch { tsMap[a.name] = null; chMap[a.name] = null; atsMap[a.name] = []; }
|
|
572
586
|
}));
|
|
573
587
|
setTaskStatsMap(tsMap);
|
|
588
|
+
setActiveTasksMap(atsMap);
|
|
574
589
|
// Persist any freshly-seeded baselines. Merge against the latest storage
|
|
575
590
|
// so a tab the user opened mid-fetch (markTabSeen) is not overwritten.
|
|
576
591
|
if (seenSeeded) {
|
|
@@ -604,6 +619,19 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
604
619
|
setExpandedAgent(prev => prev === name ? null : name);
|
|
605
620
|
}
|
|
606
621
|
|
|
622
|
+
// Running-now panel → open a specific agent's Tasks tab on a specific task.
|
|
623
|
+
// Expand the agent, record the deep-link (nonce bump re-fires repeat clicks),
|
|
624
|
+
// and scroll the agent's card into view.
|
|
625
|
+
function openAgentTask(name, taskId) {
|
|
626
|
+
setExpandedAgent(name);
|
|
627
|
+
deepLinkNonce.current += 1;
|
|
628
|
+
setDeepLink({ agent: name, taskId, nonce: deepLinkNonce.current });
|
|
629
|
+
requestAnimationFrame(() => {
|
|
630
|
+
const el = document.querySelector(`[data-agent-name="${name}"]`);
|
|
631
|
+
el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
607
635
|
async function handleSaveScopes(agentName, newScopes) {
|
|
608
636
|
try {
|
|
609
637
|
const resp = await updateAgentScopes(agentName, newScopes);
|
|
@@ -846,6 +874,11 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
846
874
|
});
|
|
847
875
|
}}
|
|
848
876
|
/>
|
|
877
|
+
<${ActiveTasksPanel}
|
|
878
|
+
activeTasksMap=${activeTasksMap}
|
|
879
|
+
agents=${agents}
|
|
880
|
+
onOpen=${openAgentTask}
|
|
881
|
+
/>
|
|
849
882
|
${renderFilterBar(agents, tagFilter, setTagFilter, groupBy, setGroupBy)}
|
|
850
883
|
${renderAgentGroups({
|
|
851
884
|
agents,
|
|
@@ -865,6 +898,7 @@ export default function AgentsTab({ session, showToast, onStats }) {
|
|
|
865
898
|
onPopOut: popOutAgent,
|
|
866
899
|
dnd,
|
|
867
900
|
agentOrder,
|
|
901
|
+
deepLink,
|
|
868
902
|
})}
|
|
869
903
|
`
|
|
870
904
|
}
|
|
@@ -930,7 +964,53 @@ function renderFilterBar(agents, tagFilter, setTagFilter, groupBy, setGroupBy) {
|
|
|
930
964
|
`;
|
|
931
965
|
}
|
|
932
966
|
|
|
933
|
-
|
|
967
|
+
// Fleet-wide "running now" panel shown between the agent board and the list.
|
|
968
|
+
// Flattens every agent's currently-active tasks into one newest-first list.
|
|
969
|
+
// Clicking a row asks the parent (onOpen) to expand that agent and open the
|
|
970
|
+
// task. Reuses the active tasks already fetched in loadData() — no extra calls.
|
|
971
|
+
function ActiveTasksPanel({ activeTasksMap, agents, onOpen }) {
|
|
972
|
+
const nameToDisplay = new Map((agents || []).map(a => [a.name, a.display_name || a.name]));
|
|
973
|
+
const rows = [];
|
|
974
|
+
for (const [name, tasks] of Object.entries(activeTasksMap || {})) {
|
|
975
|
+
for (const task of (tasks || [])) {
|
|
976
|
+
rows.push({ agentName: name, agentDisplay: nameToDisplay.get(name) || name, task });
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
rows.sort((a, b) =>
|
|
980
|
+
String(b.task.updatedAt || b.task.createdAt || '').localeCompare(
|
|
981
|
+
String(a.task.updatedAt || a.task.createdAt || '')));
|
|
982
|
+
|
|
983
|
+
// Cap the rendered rows so a very busy fleet can't produce an enormous list;
|
|
984
|
+
// the overflow is surfaced explicitly (never silently dropped).
|
|
985
|
+
const CAP = 50;
|
|
986
|
+
const shown = rows.slice(0, CAP);
|
|
987
|
+
const overflow = rows.length - shown.length;
|
|
988
|
+
|
|
989
|
+
return html`
|
|
990
|
+
<div class="pf-agd-active">
|
|
991
|
+
<div class="pf-agd-active-head">
|
|
992
|
+
<span class="section-title">${t('profile.agents.active.title')}${rows.length > 0 ? html` <span class="pf-agd-count-badge">(${rows.length})</span>` : ''}</span>
|
|
993
|
+
</div>
|
|
994
|
+
${rows.length === 0
|
|
995
|
+
? html`<div class="pf-agd-active-empty">${t('profile.agents.active.empty')}</div>`
|
|
996
|
+
: html`<div class="pf-agd-active-list">
|
|
997
|
+
${shown.map(r => html`
|
|
998
|
+
<button class="pf-agd-active-row" key=${r.task.id}
|
|
999
|
+
onClick=${() => onOpen(r.agentName, r.task.id)}
|
|
1000
|
+
title=${t('profile.agents.active.openHint')}>
|
|
1001
|
+
<span class="pf-agd-active-dot" aria-hidden="true"></span>
|
|
1002
|
+
<span class="pf-agd-active-agent">${r.agentDisplay}</span>
|
|
1003
|
+
<span class="pf-agd-active-title">${r.task.title || t('profile.agents.active.untitled')}</span>
|
|
1004
|
+
<span class="pf-agd-active-time">${timeAgo(r.task.updatedAt || r.task.createdAt)}</span>
|
|
1005
|
+
</button>
|
|
1006
|
+
`)}
|
|
1007
|
+
${overflow > 0 && html`<div class="pf-agd-active-empty">+ ${overflow} ${t('profile.agents.active.more')}</div>`}
|
|
1008
|
+
</div>`}
|
|
1009
|
+
</div>
|
|
1010
|
+
`;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function renderAgentGroups({ agents, tagFilter, groupBy, onboardings, taskStatsMap, changesMap, onTabSeen, expandedAgent, toggleAgent, session, showToast, setScopesModal, handleDeleteAgent, toggleFederate, onPopOut, dnd, agentOrder, deepLink }) {
|
|
934
1014
|
// Tag filter: agent must have ALL selected tags (intersection)
|
|
935
1015
|
const filtered = tagFilter.size === 0
|
|
936
1016
|
? agents
|
|
@@ -959,6 +1039,9 @@ function renderAgentGroups({ agents, tagFilter, groupBy, onboardings, taskStatsM
|
|
|
959
1039
|
onDeleteClick=${handleDeleteAgent}
|
|
960
1040
|
onFederateToggle=${toggleFederate}
|
|
961
1041
|
onPopOut=${onPopOut}
|
|
1042
|
+
preSelectedTab=${deepLink?.agent === a.name ? 'tasks' : null}
|
|
1043
|
+
openTaskId=${deepLink?.agent === a.name ? deepLink.taskId : null}
|
|
1044
|
+
openTaskNonce=${deepLink?.agent === a.name ? deepLink.nonce : 0}
|
|
962
1045
|
/>
|
|
963
1046
|
`;
|
|
964
1047
|
|
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
* - TaskItem -- task row with expand/collapse, todo list, start button
|
|
10
10
|
* - RequestChangesModal -- inline modal for owner to send a free-text change request
|
|
11
11
|
* @version-history
|
|
12
|
+
* v4.10.0 -- 2026-06-03 -- Deep-link support: AgentTasksSubtab accepts
|
|
13
|
+
* openTaskId + openTaskNonce; a nonce bump resets the bucket to 'recent'
|
|
14
|
+
* (clears search/time) so the target task is visible, and TaskItem auto-opens
|
|
15
|
+
* (expand + fetch events + scroll into view). Lets the fleet "running now"
|
|
16
|
+
* panel jump straight to a specific task.
|
|
17
|
+
* v4.9.1 -- 2026-06-03 -- Fix: task Scope row rendered structured provenance
|
|
18
|
+
* entries (e.g. the scheduler's { name, value, type, description }) as
|
|
19
|
+
* "[object Object]" via a naive join(). Add formatScopeEntry() and render
|
|
20
|
+
* each entry on its own line as "name: value — description".
|
|
21
|
+
* v4.9.0 -- 2026-06-03 -- Render a task's description as Markdown via the shared
|
|
22
|
+
* safe Markdown component (headings, lists, code, line breaks) instead of a
|
|
23
|
+
* single collapsed text blob, so long agent-authored prompts are readable.
|
|
12
24
|
* v4.8.3 -- 2026-06-02 -- Render non-JSON memory values (e.g. an agent's
|
|
13
25
|
* latest_output) as Markdown via the shared safe Markdown component, instead
|
|
14
26
|
* of raw monospace text.
|
|
@@ -199,6 +211,23 @@ function todoStatusIcon(status) {
|
|
|
199
211
|
return '⬜';
|
|
200
212
|
}
|
|
201
213
|
|
|
214
|
+
// Render one task scope entry as readable text. Scope is an array whose entries
|
|
215
|
+
// may be plain strings (legacy free-text scopes) or structured provenance objects
|
|
216
|
+
// stamped by the scheduler, e.g.
|
|
217
|
+
// { name:'schedule', value:'0 9 * * *', type:'cron', description:'Uutisputki – aamukirjoitus' }
|
|
218
|
+
// A naive join()/String() prints "[object Object]" for the structured form, so
|
|
219
|
+
// format the parts we know into e.g. "schedule: 0 9 * * * — Uutisputki – aamukirjoitus".
|
|
220
|
+
function formatScopeEntry(s) {
|
|
221
|
+
if (s == null) return '';
|
|
222
|
+
if (typeof s !== 'object') return String(s);
|
|
223
|
+
const head = s.name || s.type || '';
|
|
224
|
+
const val = s.value != null && s.value !== '' ? String(s.value) : '';
|
|
225
|
+
const lead = [head, val].filter(Boolean).join(': ');
|
|
226
|
+
const desc = s.description ? ` — ${s.description}` : '';
|
|
227
|
+
const out = `${lead}${desc}`.trim();
|
|
228
|
+
return out || JSON.stringify(s);
|
|
229
|
+
}
|
|
230
|
+
|
|
202
231
|
function todoProgress(todos) {
|
|
203
232
|
if (!todos || todos.length === 0) return null;
|
|
204
233
|
const active = todos.filter(td => td.status !== 'outdated');
|
|
@@ -305,9 +334,13 @@ function TaskMemoryEntry({ entry }) {
|
|
|
305
334
|
`;
|
|
306
335
|
}
|
|
307
336
|
|
|
308
|
-
function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
337
|
+
function TaskItem({ task, agentName, showToast, onRefresh, autoOpen = 0 }) {
|
|
309
338
|
const [expanded, setExpanded] = useState(false);
|
|
310
339
|
const [events, setEvents] = useState(null);
|
|
340
|
+
// Deep-link target: the parent bumps `autoOpen` (a nonce) when this exact task
|
|
341
|
+
// should be opened. taskRef scrolls it into view.
|
|
342
|
+
const taskRef = useRef(null);
|
|
343
|
+
const autoOpenedNonce = useRef(0);
|
|
311
344
|
const [loadingEvents, setLoadingEvents] = useState(false);
|
|
312
345
|
const [starting, setStarting] = useState(false);
|
|
313
346
|
const [showRevisionModal, setShowRevisionModal] = useState(false);
|
|
@@ -418,6 +451,21 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
418
451
|
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
419
452
|
}, [expanded, task.id, agentName]);
|
|
420
453
|
|
|
454
|
+
// Deep-link from the fleet "running now" panel: when autoOpen bumps to a new
|
|
455
|
+
// nonce for this task, expand it, load its events, and scroll it into view.
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
if (!autoOpen || autoOpen === autoOpenedNonce.current) return;
|
|
458
|
+
autoOpenedNonce.current = autoOpen;
|
|
459
|
+
setExpanded(true);
|
|
460
|
+
// Always refresh the event log on a fresh deep-link open (don't gate on the
|
|
461
|
+
// possibly-stale `events` closure — the task's log may have moved on since a
|
|
462
|
+
// previous open).
|
|
463
|
+
fetchEvents();
|
|
464
|
+
requestAnimationFrame(() => {
|
|
465
|
+
taskRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
466
|
+
});
|
|
467
|
+
}, [autoOpen]);
|
|
468
|
+
|
|
421
469
|
async function handleStart(e) {
|
|
422
470
|
e.stopPropagation();
|
|
423
471
|
setStarting(true);
|
|
@@ -548,7 +596,7 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
548
596
|
}
|
|
549
597
|
|
|
550
598
|
return html`
|
|
551
|
-
<div>
|
|
599
|
+
<div ref=${taskRef}>
|
|
552
600
|
<div class="pf-agd-task-item" onClick=${handleExpand}>
|
|
553
601
|
<div class="pf-agd-task-title-row">
|
|
554
602
|
<button
|
|
@@ -567,7 +615,7 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
567
615
|
</div>
|
|
568
616
|
${expanded && html`
|
|
569
617
|
<div class="pf-agd-task-expanded">
|
|
570
|
-
${task.description && html`<div class="pf-agd-task-desc"
|
|
618
|
+
${task.description && html`<div class="pf-agd-task-desc"><${Markdown} text=${task.description} /></div>`}
|
|
571
619
|
|
|
572
620
|
${task.deliverableKey && html`
|
|
573
621
|
<div class="pf-agd-deliverable">
|
|
@@ -616,10 +664,14 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
616
664
|
</div>
|
|
617
665
|
`}
|
|
618
666
|
|
|
619
|
-
${task.scope && html`
|
|
667
|
+
${task.scope && (!Array.isArray(task.scope) || task.scope.length > 0) && html`
|
|
620
668
|
<div class="pf-agd-info-row">
|
|
621
669
|
<span class="pf-agd-info-label">${t('profile.agents.detail.tasks.scope')}</span>
|
|
622
|
-
<span class="pf-agd-info-value"
|
|
670
|
+
<span class="pf-agd-info-value">
|
|
671
|
+
${Array.isArray(task.scope)
|
|
672
|
+
? task.scope.map((s, i) => html`<div key=${i}>${formatScopeEntry(s)}</div>`)
|
|
673
|
+
: formatScopeEntry(task.scope)}
|
|
674
|
+
</span>
|
|
623
675
|
</div>
|
|
624
676
|
`}
|
|
625
677
|
${task.rules && task.rules.length > 0 && html`
|
|
@@ -769,7 +821,7 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
769
821
|
`;
|
|
770
822
|
}
|
|
771
823
|
|
|
772
|
-
export default function AgentTasksSubtab({ agent, agentName, showToast }) {
|
|
824
|
+
export default function AgentTasksSubtab({ agent, agentName, showToast, openTaskId = null, openTaskNonce = 0 }) {
|
|
773
825
|
const [tasks, setTasks] = useState(null);
|
|
774
826
|
const [counts, setCounts] = useState({ recent: 0, keep: 0, archive: 0 });
|
|
775
827
|
const [bucket, setBucket] = useState('recent');
|
|
@@ -828,6 +880,17 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
|
|
|
828
880
|
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
829
881
|
}, []);
|
|
830
882
|
|
|
883
|
+
// Deep-link (from the fleet "running now" panel): a target task may live in a
|
|
884
|
+
// bucket/filter that isn't currently shown. Reset to the default Recent view
|
|
885
|
+
// (where active tasks always appear) so the target TaskItem renders and can
|
|
886
|
+
// auto-open. Keyed on the nonce so repeat clicks re-trigger.
|
|
887
|
+
useEffect(() => {
|
|
888
|
+
if (!openTaskId || !openTaskNonce) return;
|
|
889
|
+
setBucket('recent');
|
|
890
|
+
setQ('');
|
|
891
|
+
setTimeChip('all');
|
|
892
|
+
}, [openTaskId, openTaskNonce]);
|
|
893
|
+
|
|
831
894
|
function handleCreated() {
|
|
832
895
|
setShowCreate(false);
|
|
833
896
|
loadTasks();
|
|
@@ -906,7 +969,8 @@ export default function AgentTasksSubtab({ agent, agentName, showToast }) {
|
|
|
906
969
|
`}
|
|
907
970
|
|
|
908
971
|
${tasks.length > 0 ? tasks.map(task => html`
|
|
909
|
-
<${TaskItem} key=${task.id} task=${task} agentName=${agentName} showToast=${showToast} onRefresh=${loadTasks}
|
|
972
|
+
<${TaskItem} key=${task.id} task=${task} agentName=${agentName} showToast=${showToast} onRefresh=${loadTasks}
|
|
973
|
+
autoOpen=${task.id === openTaskId ? openTaskNonce : 0} />
|
|
910
974
|
`) : html`
|
|
911
975
|
<div class="pf-agd-empty">${q ? t('profile.agents.tasks.search.noResults') : t(`profile.agents.tasks.bucket.empty.${bucket}`)}</div>
|
|
912
976
|
`}
|