aimeat 1.24.0 → 1.24.1

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.
Files changed (95) hide show
  1. package/dist/locales/en.json +9 -1
  2. package/dist/locales/fi.json +9 -1
  3. package/dist/public/css/views/profile.css +21 -0
  4. package/dist/public/css/views/workflows.css +6 -1
  5. package/dist/public/js/services/organisms.js +18 -0
  6. package/dist/public/js/services/workflows.js +5 -0
  7. package/dist/public/views/profile/organisms-tab.js +51 -0
  8. package/dist/public/views/profile/workflows-tab.js +67 -11
  9. package/dist/src/generated/api-types.d.ts +286 -1
  10. package/dist/src/generated/api-types.d.ts.map +1 -1
  11. package/dist/src/mcp/annotations.d.ts.map +1 -1
  12. package/dist/src/mcp/annotations.js +2 -0
  13. package/dist/src/mcp/annotations.js.map +1 -1
  14. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  15. package/dist/src/mcp/catalog/definitions.js +17 -0
  16. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  17. package/dist/src/mcp/catalog/surfaces.js +3 -3
  18. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  19. package/dist/src/mcp/workspaces.d.ts +3 -0
  20. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  21. package/dist/src/mcp/workspaces.js +17 -0
  22. package/dist/src/mcp/workspaces.js.map +1 -1
  23. package/dist/src/models/agent-onboarding-schemas.d.ts +1 -1
  24. package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -1
  25. package/dist/src/models/agent-onboarding-schemas.js +12 -0
  26. package/dist/src/models/agent-onboarding-schemas.js.map +1 -1
  27. package/dist/src/models/offer-schemas.d.ts +2 -2
  28. package/dist/src/models/offer-schemas.d.ts.map +1 -1
  29. package/dist/src/models/offer-schemas.js +3 -1
  30. package/dist/src/models/offer-schemas.js.map +1 -1
  31. package/dist/src/models/workflow-schemas.d.ts +1 -1
  32. package/dist/src/models/workflow-schemas.d.ts.map +1 -1
  33. package/dist/src/routes/agent-skill-bundle.d.ts +1 -0
  34. package/dist/src/routes/agent-skill-bundle.d.ts.map +1 -1
  35. package/dist/src/routes/agent-skill-bundle.js +3 -2
  36. package/dist/src/routes/agent-skill-bundle.js.map +1 -1
  37. package/dist/src/routes/memory.d.ts.map +1 -1
  38. package/dist/src/routes/memory.js +4 -21
  39. package/dist/src/routes/memory.js.map +1 -1
  40. package/dist/src/routes/organisms.d.ts +4 -0
  41. package/dist/src/routes/organisms.d.ts.map +1 -1
  42. package/dist/src/routes/organisms.js +76 -0
  43. package/dist/src/routes/organisms.js.map +1 -1
  44. package/dist/src/routes/prompts.d.ts +4 -0
  45. package/dist/src/routes/prompts.d.ts.map +1 -1
  46. package/dist/src/routes/prompts.js +50 -0
  47. package/dist/src/routes/prompts.js.map +1 -1
  48. package/dist/src/routes/workflows.d.ts.map +1 -1
  49. package/dist/src/routes/workflows.js +12 -0
  50. package/dist/src/routes/workflows.js.map +1 -1
  51. package/dist/src/services/apps-backup-export.d.ts.map +1 -1
  52. package/dist/src/services/apps-backup-export.js +3 -2
  53. package/dist/src/services/apps-backup-export.js.map +1 -1
  54. package/dist/src/services/draft-offer-prompt.d.ts +19 -0
  55. package/dist/src/services/draft-offer-prompt.d.ts.map +1 -0
  56. package/dist/src/services/draft-offer-prompt.js +80 -0
  57. package/dist/src/services/draft-offer-prompt.js.map +1 -0
  58. package/dist/src/services/handbooks/agent.d.ts +6 -1
  59. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  60. package/dist/src/services/handbooks/agent.js +19 -0
  61. package/dist/src/services/handbooks/agent.js.map +1 -1
  62. package/dist/src/services/offerings-handbook.d.ts +17 -0
  63. package/dist/src/services/offerings-handbook.d.ts.map +1 -0
  64. package/dist/src/services/offerings-handbook.js +75 -0
  65. package/dist/src/services/offerings-handbook.js.map +1 -0
  66. package/dist/src/services/onboarding-validator.d.ts.map +1 -1
  67. package/dist/src/services/onboarding-validator.js +73 -0
  68. package/dist/src/services/onboarding-validator.js.map +1 -1
  69. package/dist/src/services/organism-export.d.ts.map +1 -1
  70. package/dist/src/services/organism-export.js +3 -2
  71. package/dist/src/services/organism-export.js.map +1 -1
  72. package/dist/src/services/owner-memory.d.ts +32 -0
  73. package/dist/src/services/owner-memory.d.ts.map +1 -0
  74. package/dist/src/services/owner-memory.js +37 -0
  75. package/dist/src/services/owner-memory.js.map +1 -0
  76. package/dist/src/services/package-zip.d.ts +1 -0
  77. package/dist/src/services/package-zip.d.ts.map +1 -1
  78. package/dist/src/services/package-zip.js +3 -2
  79. package/dist/src/services/package-zip.js.map +1 -1
  80. package/dist/src/services/structure-overview.d.ts +87 -0
  81. package/dist/src/services/structure-overview.d.ts.map +1 -0
  82. package/dist/src/services/structure-overview.js +234 -0
  83. package/dist/src/services/structure-overview.js.map +1 -0
  84. package/dist/src/services/workflow/engine.d.ts +7 -0
  85. package/dist/src/services/workflow/engine.d.ts.map +1 -1
  86. package/dist/src/services/workflow/engine.js +29 -0
  87. package/dist/src/services/workflow/engine.js.map +1 -1
  88. package/dist/src/services/workflow/eval-context.d.ts +5 -1
  89. package/dist/src/services/workflow/eval-context.d.ts.map +1 -1
  90. package/dist/src/services/workflow/eval-context.js +8 -2
  91. package/dist/src/services/workflow/eval-context.js.map +1 -1
  92. package/dist/src/services/workspace-export.d.ts.map +1 -1
  93. package/dist/src/services/workspace-export.js +3 -2
  94. package/dist/src/services/workspace-export.js.map +1 -1
  95. package/package.json +27 -28
@@ -45,6 +45,8 @@
45
45
  "profile.workflows.form.cancel": "Cancel",
46
46
  "profile.workflows.form.save": "Save",
47
47
  "profile.workflows.form.saved": "Workflow saved",
48
+ "profile.workflows.cancelRun": "Cancel run",
49
+ "profile.workflows.cancelled": "Run cancelled",
48
50
  "profile.tabs.scheduler": "Scheduler",
49
51
  "profile.agents.detail.tabs.schedules": "Schedules",
50
52
  "profile.scheduler.title": "Scheduler",
@@ -5897,6 +5899,9 @@
5897
5899
  "moreActions": "More actions",
5898
5900
  "policyLabel": "Join policy",
5899
5901
  "tabWorkspaces": "Workspaces",
5902
+ "structureOverviewOrg": "Organism structure overview",
5903
+ "structureOverviewWs": "Workspace structure overview",
5904
+ "structEmpty": "No structure to show yet.",
5900
5905
  "tabMembers": "Members",
5901
5906
  "tabAgents": "Agents",
5902
5907
  "tabBoard": "Board",
@@ -6402,7 +6407,10 @@
6402
6407
  "complete_test_task": "Agent executes and completes the test task",
6403
6408
  "publish_commands": "agents.{name}.commands memory has a non-empty array of { name, description, category }",
6404
6409
  "publish_config": "At least one agents.config.{name}.* memory entry exists describing this runtime",
6405
- "declare_services": "Agent declares offered services (optional)"
6410
+ "declare_services": "Agent declares offered services (optional)",
6411
+ "declare_offerings": "Agent publishes at least one offer (id + title + ask) to agents.{name}.offers so the owner can find what it does (optional)",
6412
+ "make_workflow_compatible": "At least one offer declares success_signal + required_to_function + deliverable.location, so it can be a step in an Agent Workflow (optional)",
6413
+ "price_offer": "At least one offer is sellable cross-owner: price + visibility public + a callable binding (optional)"
6406
6414
  },
6407
6415
  "errors": {
6408
6416
  "accessDenied": "Access denied",
@@ -45,6 +45,8 @@
45
45
  "profile.workflows.form.cancel": "Peruuta",
46
46
  "profile.workflows.form.save": "Tallenna",
47
47
  "profile.workflows.form.saved": "Työnkulku tallennettu",
48
+ "profile.workflows.cancelRun": "Keskeytä ajo",
49
+ "profile.workflows.cancelled": "Ajo keskeytetty",
48
50
  "profile.tabs.scheduler": "Ajastin",
49
51
  "profile.agents.detail.tabs.schedules": "Ajastukset",
50
52
  "profile.scheduler.title": "Ajastin",
@@ -5897,6 +5899,9 @@
5897
5899
  "moreActions": "Lisätoiminnot",
5898
5900
  "policyLabel": "Liittymiskäytäntö",
5899
5901
  "tabWorkspaces": "Työtilat",
5902
+ "structureOverviewOrg": "Organismin rakenteen yleiskuva",
5903
+ "structureOverviewWs": "Työtilan rakenteen yleiskuva",
5904
+ "structEmpty": "Ei vielä rakennetta näytettäväksi.",
5900
5905
  "tabMembers": "Jäsenet",
5901
5906
  "tabAgents": "Agentit",
5902
5907
  "tabBoard": "Taulu",
@@ -6402,7 +6407,10 @@
6402
6407
  "complete_test_task": "Agentti suorittaa ja viimeistelee testitehtävän",
6403
6408
  "publish_commands": "agents.{name}.commands -muistissa on epätyhjä { name, description, category } -taulu",
6404
6409
  "publish_config": "Vähintään yksi agents.config.{name}.* -muistitietue kuvaa tätä runtimea",
6405
- "declare_services": "Agentti ilmoittaa tarjoamansa palvelut (valinnainen)"
6410
+ "declare_services": "Agentti ilmoittaa tarjoamansa palvelut (valinnainen)",
6411
+ "declare_offerings": "Agentti julkaisee vähintään yhden tarjouksen (id + title + ask) avaimeen agents.{name}.offers, jotta omistaja löytää mitä se tekee (valinnainen)",
6412
+ "make_workflow_compatible": "Vähintään yksi tarjous määrittää success_signal + required_to_function + deliverable.location, jolloin se voi olla työnkulun vaihe (valinnainen)",
6413
+ "price_offer": "Vähintään yksi tarjous on myytävissä omistajien välillä: price + visibility public + kutsuttava sidonta (valinnainen)"
6406
6414
  },
6407
6415
  "errors": {
6408
6416
  "accessDenied": "Pääsy estetty",
@@ -2492,6 +2492,27 @@
2492
2492
  .pj-org-tab-count { color: var(--muted); font-size: 0.75rem; margin-left: 0.3rem; }
2493
2493
  .pj-org-boardrow { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
2494
2494
 
2495
+ /* ── OKF-style structure overview (collapsible Markdown map of an organism / workspace) ── */
2496
+ .pj-struct-overview { margin: 0.4rem 0 0.8rem; }
2497
+ .pj-struct-toggle { display: inline-flex; align-items: center; gap: 0.4rem; background: none; border: none; padding: 0.3rem 0; font-size: 0.85rem; color: var(--text-dim); cursor: pointer; }
2498
+ .pj-struct-toggle:hover { color: var(--text); }
2499
+ .pj-struct-caret { color: var(--muted); font-size: 0.8rem; width: 0.8rem; }
2500
+ .pj-struct-body { margin-top: 0.4rem; max-height: 32rem; overflow: auto; padding: 0.75rem 0.9rem; }
2501
+ /* Compact, on-theme typography so the rendered map reads as part of the dashboard, not a raw doc. */
2502
+ .pj-struct-body .md-body { font-size: 0.85rem; color: var(--text); line-height: 1.5; }
2503
+ .pj-struct-body .md-body > :first-child { margin-top: 0; }
2504
+ .pj-struct-body .md-body h1 { font-size: 1rem; font-weight: 700; margin: 0 0 0.3rem; color: var(--text); }
2505
+ .pj-struct-body .md-body h2 { font-size: 0.92rem; font-weight: 700; margin: 0.95rem 0 0.35rem; padding-top: 0.55rem; border-top: 1px solid var(--border); color: var(--text); }
2506
+ .pj-struct-body .md-body h3 { font-size: 0.83rem; font-weight: 600; margin: 0.55rem 0 0.2rem; color: var(--text-dim); text-transform: none; }
2507
+ .pj-struct-body .md-body p { margin: 0.2rem 0; }
2508
+ .pj-struct-body .md-body blockquote { margin: 0.2rem 0; padding: 0 0 0 0.6rem; border-left: 2px solid var(--border); color: var(--muted); font-size: 0.8rem; }
2509
+ .pj-struct-body .md-body blockquote p { margin: 0.1rem 0; }
2510
+ .pj-struct-body .md-body ul { margin: 0.2rem 0 0.4rem; padding-left: 1.15rem; }
2511
+ .pj-struct-body .md-body li { margin: 0.12rem 0; }
2512
+ .pj-struct-body .md-body code { background: var(--bg-dim); border: 1px solid var(--border); border-radius: 4px; padding: 0.02rem 0.32rem; font-size: 0.78rem; }
2513
+ .pj-struct-body .md-body strong { font-weight: 600; }
2514
+ .pj-struct-body .md-body em { color: var(--muted); font-style: normal; }
2515
+
2495
2516
  /* ── Workspace renew (phase 2): header actions, review banner, add-space tab ── */
2496
2517
  .pj-ws-head-actions { margin-left: auto; display: flex; gap: 0.4rem; align-items: center; }
2497
2518
  .pj-ws-banner { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; border: 1px solid var(--border); border-left: 3px solid var(--warn, var(--accent)); border-radius: 10px; background: var(--card); padding: 0.55rem 0.75rem; margin: 0.6rem 0 0.2rem; }
@@ -47,8 +47,13 @@
47
47
 
48
48
  /* ── detail ── */
49
49
  .wf-detail-head { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
50
- .wf-detail-title { font-size: 1.1rem; font-weight: 700; color: var(--text); flex: 1; }
50
+ .wf-detail-titlebox { flex: 1; display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
51
+ .wf-detail-title { font-size: 1.1rem; font-weight: 700; color: var(--text); }
52
+ .wf-detail-sub { font-size: 0.8rem; color: var(--text-dim); }
53
+ .wf-detail-sub code { font-family: var(--font-mono, monospace); }
54
+ .wf-detail-desc { color: var(--text-dim); font-size: 0.9rem; margin: -0.3rem 0 0.3rem; }
51
55
  .wf-detail-actions { display: flex; gap: 0.4rem; }
56
+ .wf-node-desc { font-weight: 400; color: var(--text-dim); font-size: 0.85rem; }
52
57
 
53
58
  .wf-graph { display: flex; flex-direction: column; gap: 0.5rem; }
54
59
  .wf-node {
@@ -491,6 +491,24 @@ export async function getWorkspace(orgId, wsId) {
491
491
  return resp?.data || null;
492
492
  }
493
493
 
494
+ /** OKF-style STRUCTURE OVERVIEW (Markdown) of the whole organism — every workspace's space
495
+ * breakdown + totals, deterministic, size-bounded. Returns '' on failure (caller shows a notice). */
496
+ export async function getOrganismOverview(orgId) {
497
+ try {
498
+ const r = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/overview`);
499
+ return r?.data?.markdown || '';
500
+ } catch { return ''; }
501
+ }
502
+
503
+ /** OKF-style STRUCTURE OVERVIEW (Markdown) of ONE workspace — per space the most-recent entries
504
+ * (ids + titles) and totals. Returns '' on failure. */
505
+ export async function getWorkspaceOverview(orgId, wsId) {
506
+ try {
507
+ const r = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/overview?ws=${encodeURIComponent(wsId)}`);
508
+ return r?.data?.markdown || '';
509
+ } catch { return ''; }
510
+ }
511
+
494
512
  /** Overwrite a workspace's manifest (e.g. edited name/summary/policy from Settings). */
495
513
  export async function saveManifest(orgId, wsId, manifest) {
496
514
  return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.manifest`, value: manifest, visibility: 'private' });
@@ -55,3 +55,8 @@ export async function getRun(id, runId) {
55
55
  export async function runWorkflow(id, mode, vars) {
56
56
  return apiPost(`/v1/workflows/${encodeURIComponent(id)}/run`, { mode, ...(vars ? { vars } : {}) });
57
57
  }
58
+
59
+ /** Cancel an in-flight run (stuck / gone wrong). */
60
+ export async function cancelRun(id, runId) {
61
+ return apiPost(`/v1/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/cancel`, {});
62
+ }
@@ -148,6 +148,11 @@
148
148
  * auto-activates; §7c) — the agent joins/provisions its own spaces and the task completion is
149
149
  * the ack. An agent already working in THIS workspace (per the participants data) shows a
150
150
  * "✓ active here" badge instead of Adopt. Convention agreed with the crewaimeat side 2026-06-10.
151
+ * v1.29.0 — 2026-06-13 — OKF-style structure overview: a collapsible <${'StructureOverview'}> panel in
152
+ * OrganismHome (📐 Organism structure overview — every workspace's space breakdown + totals) and in
153
+ * Workspace (📐 Workspace structure overview — per-space recent ids + titles). Lazy-loads a
154
+ * deterministic Markdown map from the server (orgService.getOrganismOverview / getWorkspaceOverview)
155
+ * and renders it via the safe Markdown component. Collapsed by default (no cost until opened).
151
156
  * v1.28.0 — 2026-06-12 — Overview landing tab: the whole workspace on one vertical scroll — a
152
157
  * "what happened here" strip (last 8 activity events, humanized + clickable) above every
153
158
  * manifest space stacked in order (records: compact rows max 5 + "Show all N →"; documents:
@@ -237,6 +242,46 @@ function KebabMenu({ items, label, trigger, btnClass }) {
237
242
 
238
243
  /** Tag chips + inline input — Enter/comma adds, × or Backspace-on-empty removes, blur commits.
239
244
  * Shows immediately how the value parses (vs. a raw "comma separated" text field). */
245
+ /** Collapsible OKF-style structure-overview panel. A button that, on first expand, lazy-loads a
246
+ * deterministic Markdown structure map (server projection — never persisted) and renders it via the
247
+ * safe Markdown component. Used at organism level (every workspace's space breakdown) and workspace
248
+ * level (per-space recent ids + titles). Collapsed by default so it costs nothing until asked for. */
249
+ function StructureOverview({ load, label }) {
250
+ const [open, setOpen] = useState(false);
251
+ const [md, setMd] = useState(null); // null = not loaded yet; '' = loaded but empty/failed
252
+ const [busy, setBusy] = useState(false);
253
+ const toggle = async () => {
254
+ const next = !open;
255
+ setOpen(next);
256
+ if (next && md === null && !busy) {
257
+ setBusy(true);
258
+ // For the human view, strip the OKF YAML frontmatter (machine metadata — kept in the raw
259
+ // server/MCP output) AND the leading "# … — structure overview" H1 (the panel toggle already
260
+ // names it; the page header already shows the org/workspace name). The body starts at the
261
+ // description / totals line.
262
+ try {
263
+ const raw = (await load()) || '';
264
+ setMd(raw.replace(/^---\n[\s\S]*?\n---\n+/, '').replace(/^#\s+.*\n+/, ''));
265
+ } finally { setBusy(false); }
266
+ }
267
+ };
268
+ return html`
269
+ <div class="pj-struct-overview">
270
+ <button class="pj-struct-toggle" aria-expanded=${open} onClick=${toggle}>
271
+ <span class="pj-struct-caret">${open ? '▾' : '▸'}</span>
272
+ <span>${'📐 '}${label}</span>
273
+ </button>
274
+ ${open ? html`
275
+ <div class="pj-struct-body card-detail">
276
+ ${busy
277
+ ? html`<${Spinner} text=${t('organisms.loading') || 'Loading...'} />`
278
+ : (md
279
+ ? html`<${Markdown} text=${md} />`
280
+ : html`<div class="section-desc">${t('organisms.structEmpty') || 'No structure to show yet.'}</div>`)}
281
+ </div>` : null}
282
+ </div>`;
283
+ }
284
+
240
285
  function TagInput({ tags, onChange, placeholder }) {
241
286
  const [val, setVal] = useState('');
242
287
  const add = () => { const v = val.trim().replace(/,+$/, ''); if (v && !tags.includes(v)) onChange([...tags, v]); setVal(''); };
@@ -1413,6 +1458,9 @@ function OrganismHome({ org, ghii, showToast, initialSettings, onOpenWs, onBack,
1413
1458
  </div>
1414
1459
  </div>
1415
1460
 
1461
+ <${StructureOverview} label=${t('organisms.structureOverviewOrg') || 'Organism structure overview'}
1462
+ load=${() => orgService.getOrganismOverview(org.id)} />
1463
+
1416
1464
  <div class="pj-org-tabs" role="tablist">
1417
1465
  ${tabs.map(tb => html`
1418
1466
  <button class="pj-org-tab ${activeHomeTab === tb.id ? 'active' : ''}" role="tab" aria-selected=${activeHomeTab === tb.id} key=${tb.id}
@@ -2951,6 +2999,9 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
2951
2999
  </div>
2952
3000
  ${ws.manifest?.summary ? html`<div class="section-desc">${(ws.manifest.summary)}</div>` : null}
2953
3001
 
3002
+ <${StructureOverview} label=${t('organisms.structureOverviewWs') || 'Workspace structure overview'}
3003
+ load=${() => orgService.getWorkspaceOverview(orgId, wsId)} />
3004
+
2954
3005
  ${approvals.length > 0 && activeTab !== 'review' ? html`
2955
3006
  <div class="pj-ws-banner" role="status">
2956
3007
  <span class="pj-ws-banner-text">
@@ -16,7 +16,7 @@ import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
16
16
  import htm from 'htm';
17
17
  import { t } from '/js/i18n.js';
18
18
  import { timeAgo } from '/js/utils.js';
19
- import { listWorkflows, getWorkflow, getBlueprint, getHealth, listRuns, getRun, runWorkflow } from '/js/services/workflows.js';
19
+ import { listWorkflows, getWorkflow, getBlueprint, getHealth, listRuns, getRun, runWorkflow, cancelRun } from '/js/services/workflows.js';
20
20
  import WorkflowForm from './workflows-form.js';
21
21
 
22
22
  const html = htm.bind(h);
@@ -39,7 +39,7 @@ function triggerSummary(trigger) {
39
39
  function statusClass(status) {
40
40
  if (status === 'done' || status === 'green') return 'wf-badge--ok';
41
41
  if (status === 'partial' || status === 'red' || (typeof status === 'string' && status.endsWith('-red')) || status === 'timed-out') return 'wf-badge--err';
42
- if (status === 'skipped') return 'wf-badge--muted';
42
+ if (status === 'skipped' || status === 'cancelled') return 'wf-badge--muted';
43
43
  return 'wf-badge--run';
44
44
  }
45
45
 
@@ -111,7 +111,7 @@ export default function WorkflowsTab({ showToast }) {
111
111
  if (view.name === 'edit') return html`<${EditFormView} id=${view.id} showToast=${showToast}
112
112
  onCancel=${() => setView({ name: 'detail', id: view.id })} onSaved=${(id) => setView({ name: 'detail', id })} />`;
113
113
  if (view.name === 'detail') return html`<${DetailView} id=${view.id} onBack=${() => { setView({ name: 'list' }); loadList(); }} onOpenRun=${(runId) => setView({ name: 'run', id: view.id, runId })} onRun=${doRun} onEdit=${() => setView({ name: 'edit', id: view.id })} />`;
114
- if (view.name === 'run') return html`<${RunView} id=${view.id} runId=${view.runId} onBack=${() => setView({ name: 'detail', id: view.id })} />`;
114
+ if (view.name === 'run') return html`<${RunView} id=${view.id} runId=${view.runId} showToast=${showToast} onBack=${() => setView({ name: 'detail', id: view.id })} />`;
115
115
 
116
116
  // ── list ──
117
117
  return html`
@@ -161,35 +161,55 @@ function EditFormView({ id, onCancel, onSaved, showToast }) {
161
161
  }
162
162
 
163
163
  function DetailView({ id, onBack, onOpenRun, onRun, onEdit }) {
164
+ const [def, setDef] = useState(null);
164
165
  const [blueprint, setBlueprint] = useState(null);
165
166
  const [runs, setRuns] = useState([]);
166
167
  const [err, setErr] = useState(null);
167
168
 
168
169
  const load = useCallback(async () => {
169
- const [bp, rr] = await Promise.all([getBlueprint(id).catch(e => ({ _err: e.message })), listRuns(id).catch(() => null)]);
170
+ const [d, bp, rr] = await Promise.all([
171
+ getWorkflow(id).catch(() => null),
172
+ getBlueprint(id).catch(e => ({ _err: e.message })),
173
+ listRuns(id).catch(() => null),
174
+ ]);
175
+ setDef(d?.data || null);
170
176
  if (bp?._err) setErr(bp._err); else setBlueprint(bp?.data || null);
171
177
  setRuns(rr?.data?.runs || []);
172
178
  }, [id]);
173
179
  useEffect(() => { load(); }, [load]);
180
+ // Live updates: re-fetch the blueprint + recent runs as runs advance (engine emitChange → SSE).
181
+ const liveRef = useRef(load); liveRef.current = load;
182
+ useEffect(() => {
183
+ const h = () => liveRef.current();
184
+ window.addEventListener('aimeat-live-update', h);
185
+ return () => window.removeEventListener('aimeat-live-update', h);
186
+ }, []);
187
+
188
+ const stepDesc = (stepId) => loc((def?.steps || []).find(s => s.id === stepId)?.description);
189
+ const triggerLine = def ? triggerSummary(def.trigger) : '';
174
190
 
175
191
  return html`
176
192
  <div class="wf-tab">
177
193
  <div class="wf-detail-head">
178
194
  <button class="btn-ghost" onClick=${onBack}>← ${t('profile.workflows.back')}</button>
179
- <div class="wf-detail-title">${id}</div>
195
+ <div class="wf-detail-titlebox">
196
+ <div class="wf-detail-title">${def ? loc(def.title) || id : id}</div>
197
+ <div class="wf-detail-sub"><code>${id}</code>${triggerLine ? html` · ${triggerLine}` : ''}</div>
198
+ </div>
180
199
  <div class="wf-detail-actions">
181
200
  <button class="btn-ghost" onClick=${onEdit}>${t('profile.workflows.edit')}</button>
182
201
  <button class="btn-outline" onClick=${() => onRun(id, 'signals-only')}>${t('profile.workflows.check')}</button>
183
202
  <button class="btn-primary" onClick=${() => onRun(id, 'full')}>${t('profile.workflows.run')}</button>
184
203
  </div>
185
204
  </div>
205
+ ${def && loc(def.description) && html`<div class="wf-detail-desc">${loc(def.description)}</div>`}
186
206
 
187
207
  ${err && html`<div class="wf-error">${err}</div>`}
188
208
 
189
209
  <div class="wf-section-title">${t('profile.workflows.blueprint')}</div>
190
210
  ${blueprint && html`<div class="wf-graph">
191
211
  ${blueprint.nodes.map(n => html`<div class="wf-node" key=${n.stepId}>
192
- <div class="wf-node-id">${n.stepId}</div>
212
+ <div class="wf-node-id">${n.stepId}${stepDesc(n.stepId) ? html` <span class="wf-node-desc">— ${stepDesc(n.stepId)}</span>` : ''}</div>
193
213
  <div class="wf-node-agent">${(n.agents || []).join(', ')} · ${n.offerId}</div>
194
214
  ${n.reads.length > 0 && html`<div class="wf-node-io">↘ ${n.reads.join(', ')}</div>`}
195
215
  ${n.writes.length > 0 && html`<div class="wf-node-io">↗ ${n.writes.join(', ')}</div>`}
@@ -214,30 +234,66 @@ function DetailView({ id, onBack, onOpenRun, onRun, onEdit }) {
214
234
  }
215
235
 
216
236
  // ── Run: per-step timeline ──────────────────────────────────────────────────────
217
- function RunView({ id, runId, onBack }) {
237
+ function RunView({ id, runId, onBack, showToast }) {
218
238
  const [run, setRun] = useState(null);
219
239
  const [err, setErr] = useState(null);
220
- useEffect(() => { getRun(id, runId).then(r => setRun(r?.data || null)).catch(e => setErr(e.message)); }, [id, runId]);
240
+ const [cancelling, setCancelling] = useState(false);
241
+ const loadRun = useCallback(() => getRun(id, runId).then(r => setRun(r?.data || null)).catch(e => setErr(e.message)), [id, runId]);
242
+ useEffect(() => { loadRun(); }, [loadRun]);
243
+ // Live updates: the engine emits emitChange('workflows') as the run advances (dispatch → green →
244
+ // next step → done) → SSE → aimeat-live-update. Re-fetch so the timeline updates without a manual reload.
245
+ const liveRef = useRef(loadRun); liveRef.current = loadRun;
246
+ useEffect(() => {
247
+ const h = () => liveRef.current();
248
+ window.addEventListener('aimeat-live-update', h);
249
+ return () => window.removeEventListener('aimeat-live-update', h);
250
+ }, []);
251
+
252
+ const inFlight = run && (run.status === 'running' || run.status === 'waiting-step');
253
+ const doCancel = async () => {
254
+ setCancelling(true);
255
+ try { await cancelRun(id, runId); showToast?.(t('profile.workflows.cancelled'), false); await loadRun(); }
256
+ catch (e) { showToast?.(e?.message || 'Cancel failed', true); }
257
+ finally { setCancelling(false); }
258
+ };
259
+
260
+ // The run pins the full definition (defSnapshot), so we can name the workflow + each step
261
+ // without any extra fetch — and show exactly the def THIS run executed (even if it changed since).
262
+ const def = run?.defSnapshot;
263
+ const stepDef = (stepId) => (def?.steps || []).find(s => s.id === stepId);
221
264
 
222
265
  return html`
223
266
  <div class="wf-tab">
224
267
  <div class="wf-detail-head">
225
268
  <button class="btn-ghost" onClick=${onBack}>← ${t('profile.workflows.back')}</button>
226
- <div class="wf-detail-title">${t('profile.workflows.run')} · ${run ? timeAgo(run.startedAt) : runId.slice(0, 8)}</div>
269
+ <div class="wf-detail-titlebox">
270
+ <div class="wf-detail-title">${def ? loc(def.title) || run.workflowId : t('profile.workflows.run')}</div>
271
+ <div class="wf-detail-sub">
272
+ ${run ? html`<code>${run.workflowId}</code> · ${t('profile.workflows.run')} ${timeAgo(run.startedAt)} · ${run.mode}` : runId.slice(0, 8)}
273
+ </div>
274
+ </div>
275
+ ${inFlight && html`<button class="btn-danger-solid" disabled=${cancelling} onClick=${doCancel}>${cancelling ? '…' : t('profile.workflows.cancelRun')}</button>`}
227
276
  ${run && html`<${StatusBadge} status=${run.status} />`}
228
277
  </div>
278
+ ${run && def && loc(def.description) && html`<div class="wf-detail-desc">${loc(def.description)}</div>`}
229
279
  ${err && html`<div class="wf-error">${err}</div>`}
230
280
  ${run && html`<div class="wf-steps">
231
- ${Object.entries(run.steps).map(([stepId, s]) => html`<div class="wf-step" key=${stepId}>
281
+ ${Object.entries(run.steps).map(([stepId, s]) => {
282
+ const sd = stepDef(stepId);
283
+ const agents = sd ? (Array.isArray(sd.agent) ? sd.agent.join(', ') : sd.agent) : null;
284
+ return html`<div class="wf-step" key=${stepId}>
232
285
  <div class="wf-step-row">
233
286
  <${StatusBadge} status=${s.state} />
234
287
  <span class="wf-step-id">${stepId}</span>
288
+ ${sd && loc(sd.description) && html`<span class="wf-node-desc">— ${loc(sd.description)}</span>`}
235
289
  ${s.reads?.length > 0 && html`<span class="wf-muted">↘ ${s.reads.length}</span>`}
236
290
  ${s.writes?.length > 0 && html`<span class="wf-muted">↗ ${s.writes.length}</span>`}
237
291
  ${s.attempt > 0 && html`<span class="wf-muted">${t('profile.workflows.attempt')} ${s.attempt + 1}</span>`}
238
292
  </div>
293
+ ${agents && html`<div class="wf-node-agent">${agents} · ${sd.offer}</div>`}
239
294
  ${(s.outputObserved || s.inputObserved) && html`<pre class="wf-observed">${JSON.stringify(s.outputObserved || s.inputObserved, null, 2)}</pre>`}
240
- </div>`)}
295
+ </div>`;
296
+ })}
241
297
  ${(run.inspections || []).length > 0 && html`<div class="wf-inspections">${t('profile.workflows.inspectorDispatched', { count: run.inspections.length })}</div>`}
242
298
  </div>`}
243
299
  </div>`;