aimeat 1.31.0 → 1.32.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.
Files changed (183) hide show
  1. package/dist/locales/en.json +196 -2
  2. package/dist/locales/fi.json +196 -2
  3. package/dist/public/components/InboxLink.js +29 -0
  4. package/dist/public/css/views/companies.css +65 -0
  5. package/dist/public/css/views/inbox.css +100 -1
  6. package/dist/public/css/views/members.css +33 -0
  7. package/dist/public/css/views/my-company.css +52 -0
  8. package/dist/public/css/views/profile.css +22 -0
  9. package/dist/public/js/services/messages.js +16 -0
  10. package/dist/public/js/services/notebook.js +16 -6
  11. package/dist/public/llms-template.txt +7 -0
  12. package/dist/public/spa.html +8 -0
  13. package/dist/public/views/companies.js +190 -0
  14. package/dist/public/views/members.js +42 -0
  15. package/dist/public/views/my-company.js +440 -47
  16. package/dist/public/views/portfolio-themes.js +77 -0
  17. package/dist/public/views/profile/agents-tab.js +4 -1
  18. package/dist/public/views/profile/discover-tab.js +118 -0
  19. package/dist/public/views/profile/inbox-tab.js +506 -12
  20. package/dist/public/views/profile/landing-page.js +2 -1
  21. package/dist/public/views/profile/notebook-card.js +11 -3
  22. package/dist/public/views/profile/organisms/home.js +5 -3
  23. package/dist/public/views/profile/organisms/mindmap.js +14 -3
  24. package/dist/public/views/profile/organisms/workspace.js +5 -2
  25. package/dist/public/views/profile/organisms-tab.js +12 -4
  26. package/dist/public/views/profile.js +2 -0
  27. package/dist/src/cli/connect/mcp/tools/core.d.ts.map +1 -1
  28. package/dist/src/cli/connect/mcp/tools/core.js +34 -0
  29. package/dist/src/cli/connect/mcp/tools/core.js.map +1 -1
  30. package/dist/src/cli/connect/mcp/tools/dm-messages.d.ts +1 -0
  31. package/dist/src/cli/connect/mcp/tools/dm-messages.d.ts.map +1 -1
  32. package/dist/src/cli/connect/mcp/tools/dm-messages.js +23 -0
  33. package/dist/src/cli/connect/mcp/tools/dm-messages.js.map +1 -1
  34. package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
  35. package/dist/src/cli/connect/mcp/tools/organisms.js +31 -0
  36. package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
  37. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -1
  38. package/dist/src/cli/connect/mcp/tools/workspaces.js +4 -0
  39. package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -1
  40. package/dist/src/generated/api-types.d.ts +211 -0
  41. package/dist/src/generated/api-types.d.ts.map +1 -1
  42. package/dist/src/generated/prisma-postgres/edge.js +5 -3
  43. package/dist/src/generated/prisma-postgres/index-browser.js +2 -0
  44. package/dist/src/generated/prisma-postgres/index.d.ts +89 -1
  45. package/dist/src/generated/prisma-postgres/index.js +5 -3
  46. package/dist/src/generated/prisma-postgres/package.json +1 -1
  47. package/dist/src/generated/prisma-postgres/schema.prisma +3 -0
  48. package/dist/src/generated/prisma-postgres/wasm.js +5 -3
  49. package/dist/src/mcp/annotations.d.ts.map +1 -1
  50. package/dist/src/mcp/annotations.js +1 -0
  51. package/dist/src/mcp/annotations.js.map +1 -1
  52. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  53. package/dist/src/mcp/catalog/definitions.js +18 -0
  54. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  55. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  56. package/dist/src/mcp/catalog/surfaces.js +6 -3
  57. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  58. package/dist/src/mcp/core.d.ts.map +1 -1
  59. package/dist/src/mcp/core.js +49 -0
  60. package/dist/src/mcp/core.js.map +1 -1
  61. package/dist/src/models/message-schemas.d.ts +56 -0
  62. package/dist/src/models/message-schemas.d.ts.map +1 -1
  63. package/dist/src/models/message-schemas.js +13 -0
  64. package/dist/src/models/message-schemas.js.map +1 -1
  65. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  66. package/dist/src/routes/bootstrap.js +3 -0
  67. package/dist/src/routes/bootstrap.js.map +1 -1
  68. package/dist/src/routes/discover.d.ts +22 -0
  69. package/dist/src/routes/discover.d.ts.map +1 -0
  70. package/dist/src/routes/discover.js +123 -0
  71. package/dist/src/routes/discover.js.map +1 -0
  72. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  73. package/dist/src/routes/federation-sync.js +67 -2
  74. package/dist/src/routes/federation-sync.js.map +1 -1
  75. package/dist/src/routes/messages.d.ts.map +1 -1
  76. package/dist/src/routes/messages.js +86 -1
  77. package/dist/src/routes/messages.js.map +1 -1
  78. package/dist/src/routes/organisms.d.ts +3 -0
  79. package/dist/src/routes/organisms.d.ts.map +1 -1
  80. package/dist/src/routes/organisms.js +11 -32
  81. package/dist/src/routes/organisms.js.map +1 -1
  82. package/dist/src/routes/portal.d.ts.map +1 -1
  83. package/dist/src/routes/portal.js +2 -0
  84. package/dist/src/routes/portal.js.map +1 -1
  85. package/dist/src/routes/portfolio.d.ts.map +1 -1
  86. package/dist/src/routes/portfolio.js +30 -0
  87. package/dist/src/routes/portfolio.js.map +1 -1
  88. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  89. package/dist/src/server-bootstrap/routes-loader.js +2 -0
  90. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  91. package/dist/src/services/connect-tunnel.d.ts +3 -0
  92. package/dist/src/services/connect-tunnel.d.ts.map +1 -1
  93. package/dist/src/services/connect-tunnel.js +6 -0
  94. package/dist/src/services/connect-tunnel.js.map +1 -1
  95. package/dist/src/services/discovery/__tests__/discovery.test.d.ts +2 -0
  96. package/dist/src/services/discovery/__tests__/discovery.test.d.ts.map +1 -0
  97. package/dist/src/services/discovery/__tests__/discovery.test.js +178 -0
  98. package/dist/src/services/discovery/__tests__/discovery.test.js.map +1 -0
  99. package/dist/src/services/discovery/classify.d.ts +36 -0
  100. package/dist/src/services/discovery/classify.d.ts.map +1 -0
  101. package/dist/src/services/discovery/classify.js +61 -0
  102. package/dist/src/services/discovery/classify.js.map +1 -0
  103. package/dist/src/services/discovery/facets.d.ts +21 -0
  104. package/dist/src/services/discovery/facets.d.ts.map +1 -0
  105. package/dist/src/services/discovery/facets.js +0 -0
  106. package/dist/src/services/discovery/facets.js.map +1 -0
  107. package/dist/src/services/discovery/index.d.ts +22 -0
  108. package/dist/src/services/discovery/index.d.ts.map +1 -0
  109. package/dist/src/services/discovery/index.js +22 -0
  110. package/dist/src/services/discovery/index.js.map +1 -0
  111. package/dist/src/services/discovery/normalize.d.ts +25 -0
  112. package/dist/src/services/discovery/normalize.d.ts.map +1 -0
  113. package/dist/src/services/discovery/normalize.js +108 -0
  114. package/dist/src/services/discovery/normalize.js.map +1 -0
  115. package/dist/src/services/discovery/registry.d.ts +30 -0
  116. package/dist/src/services/discovery/registry.d.ts.map +1 -0
  117. package/dist/src/services/discovery/registry.js +31 -0
  118. package/dist/src/services/discovery/registry.js.map +1 -0
  119. package/dist/src/services/discovery/run.d.ts +23 -0
  120. package/dist/src/services/discovery/run.d.ts.map +1 -0
  121. package/dist/src/services/discovery/run.js +20 -0
  122. package/dist/src/services/discovery/run.js.map +1 -0
  123. package/dist/src/services/discovery/setup.d.ts +16 -0
  124. package/dist/src/services/discovery/setup.d.ts.map +1 -0
  125. package/dist/src/services/discovery/setup.js +12 -0
  126. package/dist/src/services/discovery/setup.js.map +1 -0
  127. package/dist/src/services/discovery/sources/memory-source.d.ts +24 -0
  128. package/dist/src/services/discovery/sources/memory-source.d.ts.map +1 -0
  129. package/dist/src/services/discovery/sources/memory-source.js +183 -0
  130. package/dist/src/services/discovery/sources/memory-source.js.map +1 -0
  131. package/dist/src/services/discovery/sources/table-sources.d.ts +27 -0
  132. package/dist/src/services/discovery/sources/table-sources.d.ts.map +1 -0
  133. package/dist/src/services/discovery/sources/table-sources.js +128 -0
  134. package/dist/src/services/discovery/sources/table-sources.js.map +1 -0
  135. package/dist/src/services/discovery/types.d.ts +117 -0
  136. package/dist/src/services/discovery/types.d.ts.map +1 -0
  137. package/dist/src/services/discovery/types.js +19 -0
  138. package/dist/src/services/discovery/types.js.map +1 -0
  139. package/dist/src/services/handbooks/agent.d.ts +1 -1
  140. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  141. package/dist/src/services/handbooks/agent.js +9 -3
  142. package/dist/src/services/handbooks/agent.js.map +1 -1
  143. package/dist/src/services/message-broadcast.d.ts +49 -0
  144. package/dist/src/services/message-broadcast.d.ts.map +1 -0
  145. package/dist/src/services/message-broadcast.js +116 -0
  146. package/dist/src/services/message-broadcast.js.map +1 -0
  147. package/dist/src/services/message-delivery.d.ts.map +1 -1
  148. package/dist/src/services/message-delivery.js +2 -0
  149. package/dist/src/services/message-delivery.js.map +1 -1
  150. package/dist/src/services/message-send.d.ts +6 -0
  151. package/dist/src/services/message-send.d.ts.map +1 -1
  152. package/dist/src/services/message-send.js +7 -5
  153. package/dist/src/services/message-send.js.map +1 -1
  154. package/dist/src/services/presence.d.ts +8 -0
  155. package/dist/src/services/presence.d.ts.map +1 -1
  156. package/dist/src/services/presence.js +38 -0
  157. package/dist/src/services/presence.js.map +1 -1
  158. package/dist/src/services/workspace-enrichment.d.ts +15 -5
  159. package/dist/src/services/workspace-enrichment.d.ts.map +1 -1
  160. package/dist/src/services/workspace-enrichment.js +52 -14
  161. package/dist/src/services/workspace-enrichment.js.map +1 -1
  162. package/dist/src/storage/interface.d.ts +5 -0
  163. package/dist/src/storage/interface.d.ts.map +1 -1
  164. package/dist/src/storage/providers/mongodb/index.d.ts +1 -0
  165. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  166. package/dist/src/storage/providers/mongodb/index.js +13 -0
  167. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  168. package/dist/src/storage/providers/sqlite/index.d.ts +1 -0
  169. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  170. package/dist/src/storage/providers/sqlite/index.js +3 -0
  171. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  172. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts +3 -0
  173. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts.map +1 -1
  174. package/dist/src/storage/providers/sqlite/repos/direct-message.js +13 -2
  175. package/dist/src/storage/providers/sqlite/repos/direct-message.js.map +1 -1
  176. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  177. package/dist/src/storage/providers/sqlite/schema.js +5 -0
  178. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  179. package/dist/src/storage/repositories/direct-message.repository.d.ts +3 -0
  180. package/dist/src/storage/repositories/direct-message.repository.d.ts.map +1 -1
  181. package/package.json +1 -1
  182. package/prisma/schema.postgres.prisma +3 -0
  183. package/prisma/schema.prisma +3 -0
@@ -163,7 +163,9 @@
163
163
  }
164
164
 
165
165
  /* ── Right pane ── */
166
- .inbox-panel { display: flex; flex-direction: column; min-width: 0; height: 100%; }
166
+ /* min-height:0 lets this grid item respect the fixed .inbox-body height instead of growing to fit a tall
167
+ thread (grid/flex items default to min-height:auto = content size). */
168
+ .inbox-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; height: 100%; }
167
169
  .inbox-panel--empty { align-items: center; justify-content: center; }
168
170
 
169
171
  .inbox-empty { text-align: center; color: var(--text-dim); display: flex; flex-direction: column; align-items: center; gap: 8px; }
@@ -182,6 +184,7 @@
182
184
  /* Message scroll area. */
183
185
  .inbox-msgs {
184
186
  flex: 1;
187
+ min-height: 0; /* required so the flex child scrolls instead of growing past the composer */
185
188
  overflow-y: auto;
186
189
  padding: 16px;
187
190
  display: flex;
@@ -476,3 +479,99 @@
476
479
  /* Read-only summary once the question has been answered. */
477
480
  .inbox-iform--done { gap: 8px; }
478
481
  .inbox-iform-answered { font-size: 13px; color: var(--text-dim); }
482
+
483
+ /* ── Broadcast / send-to-many compose ── */
484
+ .inbox-bc-mode { display: flex; gap: 8px; flex-wrap: wrap; }
485
+ .inbox-bc-modeopt {
486
+ display: flex; align-items: center; gap: 6px;
487
+ padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
488
+ background: var(--card-bg); cursor: pointer; font-size: 13px;
489
+ }
490
+ .inbox-bc-modeopt input { accent-color: var(--accent); }
491
+ .inbox-bc-modeopt--on { border-color: var(--accent); background: var(--accent-subtle); }
492
+ .inbox-bc-chips { display: flex; flex-wrap: wrap; gap: 6px; }
493
+ .inbox-bc-chip {
494
+ display: inline-flex; align-items: center; gap: 6px;
495
+ padding: 3px 6px 3px 10px; border: 1px solid var(--accent-border); border-radius: var(--radius-full);
496
+ background: var(--accent-subtle); font-size: 12px; max-width: 100%;
497
+ }
498
+ .inbox-bc-chip-x {
499
+ border: none; background: transparent; color: var(--text-dim); cursor: pointer;
500
+ font-size: 12px; line-height: 1; padding: 2px 4px; border-radius: var(--radius-full);
501
+ }
502
+ .inbox-bc-chip-x:hover { color: var(--danger); background: var(--card-bg); }
503
+ .inbox-bc-add { display: flex; gap: 8px; }
504
+ .inbox-bc-add .inbox-input { flex: 1; }
505
+ /* Operator audience select (all node / all federation users). */
506
+ .inbox-bc-audience--on { border-color: var(--accent); background: var(--accent-subtle); }
507
+
508
+ /* Announcement note shown in place of the reply composer (read-only thread). */
509
+ .inbox-announce-note {
510
+ border-top: 1px solid var(--border-subtle);
511
+ padding: 12px 16px; color: var(--text-dim); font-size: 13px; text-align: center;
512
+ }
513
+
514
+ /* ── Poll builder (compose a poll broadcast) ── */
515
+ .inbox-poll-builder { display: flex; flex-direction: column; gap: 12px; }
516
+ .inbox-poll-q {
517
+ display: flex; flex-direction: column; gap: 8px;
518
+ border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px; background: var(--card-bg);
519
+ }
520
+ .inbox-poll-q-head { display: flex; align-items: center; gap: 8px; }
521
+ .inbox-poll-q-head .inbox-input { flex: 1; }
522
+ .inbox-poll-q-n { font-weight: 600; color: var(--text-dim); }
523
+ .inbox-poll-opts { display: flex; flex-direction: column; gap: 6px; padding-left: 12px; }
524
+ .inbox-poll-opt { display: flex; align-items: center; gap: 6px; }
525
+ .inbox-poll-opt .inbox-input { flex: 1; }
526
+ .inbox-poll-flags { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; color: var(--text-dim); }
527
+ .inbox-poll-flags label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
528
+ .inbox-poll-flags input { accent-color: var(--accent); }
529
+
530
+ /* ── Broadcast / poll results ── */
531
+ .inbox-results-summary { font-size: 13px; color: var(--text-dim); padding-bottom: 8px; border-bottom: 1px solid var(--border-subtle); }
532
+ .inbox-results-q { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; }
533
+ .inbox-results-prompt { font-weight: 600; font-size: 14px; }
534
+ .inbox-results-bar { display: flex; flex-direction: column; gap: 3px; }
535
+ .inbox-results-bar-label { display: flex; justify-content: space-between; font-size: 13px; }
536
+ .inbox-results-bar-track { height: 8px; border-radius: var(--radius-full); background: var(--card-bg-alt); overflow: hidden; }
537
+ .inbox-results-bar-fill { height: 100%; width: var(--w, 0%); background: var(--accent); border-radius: var(--radius-full); transition: width 0.3s; }
538
+ .inbox-results-others { font-size: 12px; color: var(--text-dim); }
539
+ .inbox-results-reclist { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; }
540
+ .inbox-results-rec { display: flex; justify-content: space-between; font-size: 13px; color: var(--text-dim); }
541
+
542
+ /* ── Agent capabilities in chat: command chips + fill-in form (Phase A) ── */
543
+ .inbox-cmdbar {
544
+ display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
545
+ padding: 8px 12px; border-top: 1px solid var(--border-subtle);
546
+ }
547
+ .inbox-cmdbar-label { font-size: 12px; color: var(--text-dim); margin-right: 2px; }
548
+ .inbox-cmd-chip {
549
+ border: 1px solid var(--accent-border); background: var(--accent-subtle); color: var(--text);
550
+ font-size: 12px; padding: 4px 10px; border-radius: var(--radius-full); cursor: pointer;
551
+ }
552
+ .inbox-cmd-chip:hover { border-color: var(--accent); }
553
+ .inbox-cmdfill {
554
+ display: flex; flex-direction: column; gap: 8px;
555
+ padding: 10px 12px; border-top: 1px solid var(--border-subtle); background: var(--card-bg-alt);
556
+ }
557
+ .inbox-cmdfill-head { display: flex; align-items: center; justify-content: space-between; font-weight: 600; font-size: 13px; }
558
+ .inbox-cmdfill-desc { font-size: 12px; color: var(--text-dim); }
559
+ .inbox-cmdfill-field { display: flex; flex-direction: column; gap: 3px; }
560
+ .inbox-cmdfill-pname { font-size: 12px; color: var(--text-dim); }
561
+ .inbox-cmdfill-go { align-self: flex-start; }
562
+
563
+ /* ── Agent schedule panel (Phase B — own agents only) ── */
564
+ .inbox-sched-btn { margin-left: auto; }
565
+ .inbox-sched-btn--on { background: var(--accent-subtle); border-color: var(--accent); }
566
+ .inbox-sched {
567
+ display: flex; flex-direction: column; gap: 10px;
568
+ padding: 10px 12px; border-top: 1px solid var(--border-subtle); background: var(--card-bg-alt);
569
+ }
570
+ .inbox-sched-head { display: flex; align-items: center; justify-content: space-between; font-weight: 600; font-size: 13px; }
571
+ .inbox-sched-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
572
+ .inbox-sched-item { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; }
573
+ .inbox-sched-name { color: var(--text); }
574
+ .inbox-sched-cron { color: var(--text-dim); font-family: var(--font-mono, monospace); font-size: 12px; }
575
+ .inbox-sched-new { display: flex; flex-direction: column; gap: 6px; }
576
+ .inbox-sched-new .btn-primary { align-self: flex-start; }
577
+ .inbox-sched-desc { min-height: 38px; resize: vertical; }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file members.css
3
+ * @description Node members showcase grid (published-portfolio owners). Card grid with avatar + name +
4
+ * bio, linking to each member's portfolio.
5
+ * @version-history
6
+ * v1.0.0 — 2026-06-23 — Initial.
7
+ */
8
+ .mbr { max-width: 1100px; margin: 0 auto; padding: 24px 16px; }
9
+ .mbr-title { font-size: 1.6rem; margin: 0 0 6px; }
10
+ .mbr-desc { color: var(--text-dim); margin: 0 0 24px; }
11
+ .mbr-empty { color: var(--text-dim); text-align: center; padding: 40px; }
12
+ .mbr-grid {
13
+ display: grid;
14
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
15
+ gap: 16px;
16
+ }
17
+ .mbr-card {
18
+ display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
19
+ padding: 20px 14px; border: 1px solid var(--card-border, var(--border)); border-radius: var(--radius-md, 12px);
20
+ background: var(--card-bg); text-decoration: none; color: inherit;
21
+ transition: border-color 0.12s, transform 0.12s;
22
+ }
23
+ .mbr-card:hover { border-color: var(--accent); transform: translateY(-2px); }
24
+ .mbr-avatar {
25
+ width: 64px; height: 64px; border-radius: var(--radius-full, 50%);
26
+ display: flex; align-items: center; justify-content: center;
27
+ font-size: 32px; background: var(--accent-subtle); border: 1px solid var(--accent-border);
28
+ }
29
+ .mbr-name { font-weight: 600; }
30
+ .mbr-bio {
31
+ font-size: 13px; color: var(--text-dim);
32
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
33
+ }
@@ -217,11 +217,62 @@
217
217
  border-radius: var(--radius-sm); padding: 0.5rem 0.7rem;
218
218
  }
219
219
  .mc-listed { color: var(--success); font-size: 0.8rem; font-weight: 600; }
220
+ .mc-pick-agents { display: flex; flex-direction: column; gap: 0.75rem; }
221
+ .mc-pick-agent { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.5rem 0.6rem; background: var(--bg-elevated); }
222
+ .mc-pick-agent-name { font-weight: 600; color: var(--text); font-size: 0.85rem; margin-bottom: 0.35rem; }
223
+ .mc-pick-none { color: var(--text-muted); }
224
+
225
+ .mc-manage { border: 1px dashed var(--border); border-radius: var(--radius-sm); padding: 0.5rem 0.6rem; margin: 0.4rem 0 0.6rem; }
226
+ .mc-manage-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
227
+ .mc-config { display: flex; flex-direction: column; gap: 0.2rem; color: var(--text-dim); font-size: 0.85rem; }
228
+ .mc-config b { color: var(--text); }
229
+ .mc-manage-row { align-items: center; gap: 0.4rem; }
230
+ .mc-price-input { width: 5rem; }
231
+ .mc-saved { color: var(--success); }
232
+ .mc-warn { color: var(--text-muted); }
233
+ .mc-remove { color: var(--danger); }
220
234
 
221
235
  .mc-cat-item { list-style: none; margin-bottom: 0.6rem; }
222
236
  .of-card-head--static { cursor: default; }
223
237
  .mc-not-callable { color: var(--text-muted); font-size: 0.75rem; margin: 0.2rem 0; }
224
238
 
239
+ .mc-subtabs { display: flex; gap: 0.25rem; border-bottom: 1px solid var(--border); margin: 0.5rem 0 1rem; flex-wrap: wrap; }
240
+ .mc-subtab {
241
+ background: transparent; border: none; border-bottom: 2px solid transparent;
242
+ padding: 0.4rem 0.7rem; cursor: pointer; color: var(--text-dim); font-size: 0.9rem; font-weight: 600;
243
+ }
244
+ .mc-subtab:hover { color: var(--text); }
245
+ .mc-subtab.active { color: var(--accent-bright); border-bottom-color: var(--accent); }
246
+ .mc-role-chip { margin-left: auto; align-self: center; font-size: 0.72rem; font-weight: 600; color: var(--text-dim); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-full); padding: 0.1rem 0.55rem; }
247
+
248
+ .mc-cust-list, .mc-member-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
249
+ .mc-cust { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.6rem 0.75rem; }
250
+ .mc-cust-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
251
+ .mc-cust-name { color: var(--text); font-weight: 600; }
252
+ .mc-cust-spend { color: var(--accent-bright); font-weight: 600; }
253
+ .mc-cust-meta { color: var(--text-muted); font-size: 0.78rem; margin: 0.2rem 0 0.4rem; }
254
+
255
+ .mc-member { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.5rem 0.7rem; }
256
+ .mc-member-name { color: var(--text); font-weight: 600; }
257
+
258
+ .mc-head-id { display: flex; align-items: center; gap: 0.75rem; }
259
+ .mc-head-logo { width: 44px; height: 44px; border-radius: var(--radius-sm); object-fit: cover; border: 1px solid var(--border); background: var(--bg-elevated); }
260
+ .mc-logo-row { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.3rem; }
261
+ .mc-logo-preview { width: 56px; height: 56px; border-radius: var(--radius-sm); object-fit: cover; border: 1px solid var(--border); background: var(--bg-elevated); }
262
+ .mc-logo-empty { width: 56px; height: 56px; border-radius: var(--radius-sm); border: 1px dashed var(--border); display: flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 0.7rem; text-align: center; }
263
+ .mc-upload-btn { position: relative; overflow: hidden; cursor: pointer; }
264
+ .mc-file-hidden { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
265
+ .mc-checkbox-row { display: flex; align-items: center; gap: 0.5rem; color: var(--text); font-size: 0.9rem; cursor: pointer; }
266
+
267
+ .mc-portfolio { display: flex; flex-direction: column; gap: 0.6rem; }
268
+ .mc-pf-live { background: var(--success-bg); border: 1px solid var(--success); border-radius: var(--radius-sm); padding: 0.6rem 0.75rem; display: flex; flex-direction: column; gap: 0.4rem; }
269
+ .mc-pf-prompt { font-family: var(--font-mono, monospace); font-size: 0.8rem; white-space: pre; }
270
+ .mc-pf-html { font-family: var(--font-mono, monospace); font-size: 0.8rem; }
271
+ .mc-theme-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.3rem 0 0.6rem; }
272
+ .mc-theme-btn { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.4rem 0.9rem; cursor: pointer; color: var(--text-dim); font-weight: 600; font-size: 0.85rem; transition: border-color 0.15s, color 0.15s; }
273
+ .mc-theme-btn:hover { color: var(--text); }
274
+ .mc-theme-btn.active { border-color: var(--accent); color: var(--accent-bright); background: var(--accent-faint); }
275
+
225
276
  .mc-header-row { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; }
226
277
 
227
278
  .mc-orders-received { border-top: 1px solid var(--border); margin-top: 1.25rem; padding-top: 1rem; }
@@ -234,6 +285,7 @@
234
285
  .mc-order-title { color: var(--text); font-weight: 600; }
235
286
  .mc-order-meta { display: flex; flex-wrap: wrap; gap: 0.6rem; color: var(--text-muted); font-size: 0.78rem; margin-top: 0.2rem; }
236
287
  .mc-order-input { color: var(--text-dim); font-size: 0.85rem; margin-top: 0.3rem; }
288
+ .mc-order-receipt { color: var(--accent-bright); font-size: 0.82rem; margin-top: 0.3rem; }
237
289
 
238
290
  .mc-status { font-size: 0.72rem; font-weight: 600; padding: 0.1rem 0.55rem; border-radius: var(--radius-full); white-space: nowrap; }
239
291
  .mc-status--queued { background: var(--bg-card); color: var(--text-dim); border: 1px solid var(--border); }
@@ -482,6 +482,7 @@
482
482
  .pf .scope-modal {max-width:560px}
483
483
  .pf .scope-modal h3 {margin-bottom:.25rem}
484
484
  .pf .scope-modal .scope-agent-info {color:var(--text-dim,#6B7280);font-size:.8rem;margin-bottom:1.25rem;font-family:monospace}
485
+ .pf .scope-agent-msg {margin-left:.5rem;text-decoration:none;font-family:initial}
485
486
  .pf .scope-templates {display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1.25rem}
486
487
  .pf .scope-tpl-btn {flex:1;min-width:100px;padding:.6rem .75rem;background:var(--bg-card,#FFFFFF);border:1px solid var(--border,#E5E7EB);border-radius:8px;cursor:pointer;color:var(--text-dim,#6B7280);font-size:.8rem;font-weight:600;transition:all .2s;text-align:center}
487
488
  .pf .scope-tpl-btn:hover {color:var(--text,#1A1A2E);border-color:var(--accent,#E8564A)}
@@ -3027,3 +3028,24 @@
3027
3028
  .pf-presence-pill--on { background: var(--accent); border-color: var(--accent); color: #fff; }
3028
3029
  .pf-presence-pill:disabled { opacity: .6; cursor: default; }
3029
3030
  .pf-presence-hint { font-size: .78rem; color: var(--text-dim); margin: .15rem 0 .35rem; }
3031
+
3032
+ /* ── Discover tab (master directory) ───────────────────────────────────── */
3033
+ .dsc-controls { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; margin: 1rem 0 .75rem; }
3034
+ .dsc-scope { display: inline-flex; gap: .25rem; background: var(--bg-dim); border: 1px solid var(--border); border-radius: var(--radius-full); padding: .2rem; }
3035
+ .dsc-scope-btn { border-radius: var(--radius-full); padding: .3rem .85rem; font-size: .85rem; }
3036
+ .dsc-scope-active { background: var(--accent); color: #fff; }
3037
+ .dsc-search { flex: 1; min-width: 200px; padding: .5rem .75rem; background: var(--control-bg); border: 1px solid var(--control-border); border-radius: var(--radius); color: inherit; }
3038
+ .dsc-facets { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1rem; }
3039
+ .dsc-facet { padding: .3rem .7rem; font-size: .82rem; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-full); cursor: pointer; color: inherit; }
3040
+ .dsc-facet:hover { border-color: var(--accent-border); }
3041
+ .dsc-facet-active { background: var(--accent-subtle); border-color: var(--accent-border); color: var(--accent); }
3042
+ .dsc-item { display: block; padding: .75rem .9rem; margin-bottom: .6rem; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); text-decoration: none; color: inherit; transition: border-color .12s; }
3043
+ .dsc-item:hover { border-color: var(--accent-border); box-shadow: var(--shadow-sm); }
3044
+ .dsc-item-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
3045
+ .dsc-type-badge { font-size: .72rem; text-transform: uppercase; letter-spacing: .03em; color: var(--accent); background: var(--accent-subtle); border: 1px solid var(--accent-border); border-radius: var(--radius-full); padding: .1rem .5rem; }
3046
+ .dsc-item-title { font-size: .98rem; }
3047
+ .dsc-segment { font-size: .75rem; color: var(--text-dim); background: var(--bg-dim); border-radius: var(--radius-full); padding: .1rem .5rem; }
3048
+ .dsc-item-desc { margin-top: .35rem; font-size: .87rem; color: var(--text-dim); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
3049
+ .dsc-item-meta { margin-top: .5rem; display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
3050
+ .dsc-tag { font-size: .72rem; color: var(--muted); background: var(--bg-dim); border-radius: var(--radius-full); padding: .08rem .45rem; }
3051
+ .dsc-owner { margin-left: auto; font-size: .72rem; color: var(--muted); font-family: var(--mono); }
@@ -26,6 +26,22 @@ export async function send({ to, body, attachments, replyTo, subject, conversati
26
26
  });
27
27
  }
28
28
 
29
+ /** Send one message to MANY recipients. `to` is an array of identities and/or `groupId` is a Share Group
30
+ * whose members are the audience. `mode` 'announcement' = read-only; 'broadcast' = repliable. `interactive`
31
+ * makes it a poll. Returns { broadcast_id, recipients, sent, failed }. */
32
+ export async function sendBroadcast({ to, groupId, audience, mode, body, attachments, interactive } = /** @type {{ to?: any, groupId?: any, audience?: any, mode?: any, body?: any, attachments?: any, interactive?: any }} */ ({})) {
33
+ return api('/v1/messages/broadcast', {
34
+ method: 'POST',
35
+ body: JSON.stringify({ to, group_id: groupId, audience, mode, body, attachments, interactive }),
36
+ });
37
+ }
38
+
39
+ /** Aggregated results for a broadcast (recipients, delivered/read/answered counts, poll answers). */
40
+ export async function getBroadcastResults(broadcastId) {
41
+ const r = await apiGet(`/v1/messages/broadcast/${enc(broadcastId)}`);
42
+ return r?.data || null;
43
+ }
44
+
29
45
  export async function listInbox(unreadOnly = false) {
30
46
  const r = await apiGet(`/v1/messages/inbox${unreadOnly ? '?unread=true' : ''}`);
31
47
  return r?.data || { messages: [], total: 0, unread: 0 };
@@ -9,7 +9,7 @@
9
9
  * @structure
10
10
  * - classifyNote(text) — POST /v1/librarian/classify
11
11
  * - distributeNote(text) — POST /v1/librarian/distribute (split into placed chunks)
12
- * - materializeDocument(plan) — resolve/create org → ws → document space → write doc → drop source
12
+ * - materializeDocument(plan) — resolve/create org → ws → document space → draft→publish doc → drop source
13
13
  * - distributeChunks(chunks, sourceKey, onProgress) — materialize each chunk to its home
14
14
  * - getNotebookSettings/saveNotebookSettings — the per-owner trust toggles (notebook.settings)
15
15
  * @version-history
@@ -18,10 +18,13 @@
18
18
  * v1.2.0 — 2026-06-21 — (reverted) materializeRecord/patchRecord removed — the Tracked Response flow
19
19
  * now writes records through the proper workspace draft→publish path (services/organisms.js), not a
20
20
  * raw memory write, so records land in the right namespace/owner and show up with activity.
21
+ * v1.3.0 — 2026-06-23 — materializeDocument now writes via writeDraft + publishDraft (was a raw
22
+ * `.latest` memory write) so notebook documents get version history and appear in the workspace
23
+ * activity log/heatmap — the document-path counterpart to the v1.2.0 record fix.
21
24
  */
22
25
  import { api, apiPost } from '/js/api.js';
23
26
  import { createMemory, getMemory, deleteMemory } from '/js/services/memory.js';
24
- import { createOrganism, saveManifest, listWorkspaces, saveWorkspaceRegistry, wsRoot } from '/js/services/organisms.js';
27
+ import { createOrganism, saveManifest, listWorkspaces, saveWorkspaceRegistry, wsRoot, writeDraft, publishDraft } from '/js/services/organisms.js';
25
28
 
26
29
  const DOC_SPACE = 'pages';
27
30
 
@@ -162,11 +165,18 @@ export async function materializeDocument(plan) {
162
165
  }
163
166
  }
164
167
 
165
- // 4. Write the document.
168
+ // 4. Write the document through the proper workspace draft→publish path (NOT a raw `.latest` write).
169
+ // A raw write to `…{space}.{docId}.latest` lands the content but produces no version history and
170
+ // no activity event (the feed is derived from `.draft`/`.version.N` keys), so notebook documents
171
+ // used to be invisible in the workspace activity log / heatmap. Going through writeDraft +
172
+ // publishDraft mints `.version.1` + `.latest` and shows up like every other published object —
173
+ // matching the Tracked-Response record path migrated in v1.2.0.
166
174
  const docId = rid('doc-');
167
- const key = `${wsRoot(organismId, workspaceId)}.${space}.${docId}.latest`;
168
- const resp = await createMemory(key, { id: docId, title: plan.title, markdown: plan.markdown }, 'private');
169
- if (resp?.ok === false) throw new Error(resp.error?.message || 'Could not write document');
175
+ const docValue = { id: docId, title: plan.title, markdown: plan.markdown };
176
+ const draftResp = await writeDraft(organismId, workspaceId, space, docId, docValue);
177
+ if (draftResp?.ok === false) throw new Error(draftResp.error?.message || 'Could not write document');
178
+ const pubResp = await publishDraft(organismId, workspaceId, space, docId);
179
+ if (pubResp?.ok === false) throw new Error(pubResp.error?.message || 'Could not publish document');
170
180
 
171
181
  // 5. Drop the source inbox note (it has been filed).
172
182
  if (plan.sourceKey) await deleteMemory(plan.sourceKey).catch(() => {});
@@ -285,6 +285,7 @@ entry: index.html
285
285
 
286
286
  ### Other options (not app building)
287
287
 
288
+ - **Discover everything from one place:** `GET /v1/discover` — the master directory. One faceted query across all domains (capabilities, workflows, knowledge, decisions, companies+offerings, apps, documents, memory). `?mode=map` (or `/v1/discover/facets`) returns counts by type/tag so you can see what exists before pulling content; `scope=own|public|shared`. MCP: `aimeat_discover`.
288
289
  - Browse this node: `GET /v1/catalogue`, `GET /v1/apps`, `GET /v1/stats`
289
290
  - Connect as AI agent: see "Connecting: Device Authorization" section below
290
291
  - Anonymous quick test: `POST /v1/auth/anonymous`
@@ -1668,6 +1669,12 @@ Discover actions, agents, and boards available on this node.
1668
1669
 
1669
1670
  #### Endpoints
1670
1671
 
1672
+ GET {{BASE_URL}}/v1/discover — Master directory: unified cross-domain discovery (start here)
1673
+ Query: ?mode=find&scope=own|public|shared&q=text&type=capability,knowledge&tags=finance&segment=research&page=1&per_page=20
1674
+ → 200: { "ok": true, "data": { "entries": [{ "type": "capability", "segment": "manual", "id": "...", "title": "...", "description": "...", "tags": [...], "visibility": "public", "owner": "alice@node", "updatedAt": "...", "href": "/v1/..." }], "total": 42, "scope": "own", "facets": { "types": [...], "segments": [...], "tags": [...] } } }
1675
+ GET {{BASE_URL}}/v1/discover/facets — Map mode: counts by type/segment/tag only (cheap "what exists?" probe), same filters + scope
1676
+ → 200: { "ok": true, "data": { "scope": "public", "total": 42, "types": [{ "value": "knowledge", "count": 18 }], "segments": [...], "tags": [...] } }
1677
+
1671
1678
  GET {{BASE_URL}}/v1/catalogue — Full catalogue listing
1672
1679
  Query: ?search=translate&category=language&page=1&per_page=20&include_federated=true
1673
1680
  → 200: { "ok": true, "data": { "actions": [{ "id": "...", "display_name": "...", "description": "...", "provider_gaii": "...", "category": "...", "pricing": { "base_morsels": 5 }, "tags": [...] }], "total": 42 } }
@@ -86,11 +86,13 @@
86
86
  <link rel="stylesheet" href="/css/views/aimeat-os.css">
87
87
  <link rel="stylesheet" href="/css/views/openclaw.css">
88
88
  <link rel="stylesheet" href="/css/views/portfolio.css">
89
+ <link rel="stylesheet" href="/css/views/members.css">
89
90
  <link rel="stylesheet" href="/css/views/inbox.css">
90
91
  <link rel="stylesheet" href="/css/components/tags.css">
91
92
  <link rel="stylesheet" href="/css/components/image-deliverable.css">
92
93
  <link rel="stylesheet" href="/css/views/help.css">
93
94
  <link rel="stylesheet" href="/css/views/my-company.css">
95
+ <link rel="stylesheet" href="/css/views/companies.css">
94
96
  <link rel="stylesheet" href="/css/views/capabilities.css">
95
97
  <link rel="stylesheet" href="/css/views/agents-detail.css">
96
98
  <link rel="stylesheet" href="/css/views/scheduler.css">
@@ -188,6 +190,7 @@
188
190
  "/components/Modal.js": "/components/Modal.js",
189
191
  "/components/useViewCSS.js": "/components/useViewCSS.js",
190
192
  "/components/Markdown.js": "/components/Markdown.js",
193
+ "/components/InboxLink.js": "/components/InboxLink.js",
191
194
  "/components/JsonView.js": "/components/JsonView.js",
192
195
  "/components/ImageDeliverable.js": "/components/ImageDeliverable.js",
193
196
  "/components/offer-card-view.js": "/components/offer-card-view.js",
@@ -226,6 +229,7 @@
226
229
  "/js/services/admin-agent-integration.js": "/js/services/admin-agent-integration.js",
227
230
  "/js/services/schedules.js": "/js/services/schedules.js",
228
231
  "/js/services/workflows.js": "/js/services/workflows.js",
232
+ "/views/portfolio-themes.js": "/views/portfolio-themes.js",
229
233
  "/views/profile/shared.js": "/views/profile/shared.js",
230
234
  "/views/profile/generator-settings.js": "/views/profile/generator-settings.js",
231
235
  "/views/profile/organisms/helpers.js": "/views/profile/organisms/helpers.js",
@@ -341,6 +345,7 @@
341
345
  '/v1/business': () => import('/views/business.js' + B),
342
346
  '/v1/profile': () => import('/views/profile.js' + B),
343
347
  '/v1/my-company': () => import('/views/my-company.js' + B),
348
+ '/v1/companies': () => import('/views/companies.js' + B),
344
349
  '/v1/app-grant': () => import('/views/app-grant.js' + B),
345
350
  '/v1/guides': () => import('/views/guides.js' + B),
346
351
  '/v1/hobbies': () => import('/views/hobbies.js' + B),
@@ -349,6 +354,7 @@
349
354
  '/v1/openclaw': () => import('/views/openclaw.js' + B),
350
355
  '/v1/classic': () => import('/views/portal-classic.js' + B),
351
356
  '/v1/portfolio': () => import('/views/portfolio.js' + B),
357
+ '/v1/members': () => import('/views/members.js' + B),
352
358
  '/v1/admin': () => import('/views/admin.js' + B),
353
359
  '/v1/help': () => import('/views/help.js' + B),
354
360
  '/v1/publicknowledgeviewer': () => import('/views/public-knowledge-viewer.js' + B),
@@ -511,6 +517,8 @@
511
517
  { id: 'try', path: '/v1/portal', key: 'nav.try' },
512
518
  { id: 'howItWorks', path: '/v1/how-it-works', key: 'nav.howItWorks', fallback: 'How it works' },
513
519
  { id: 'business', path: '/v1/business', key: 'nav.business', fallback: 'For your business' },
520
+ { id: 'companies', path: '/v1/companies', key: 'nav.companies', fallback: 'Companies' },
521
+ { id: 'members', path: '/v1/members', key: 'nav.members', fallback: 'Members' },
514
522
  { id: 'devView', path: '/v1/portal?view=dev', key: 'nav.devView', matchActive: false },
515
523
  { id: 'help', path: '/v1/help', key: 'nav.help' },
516
524
  ];
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @file companies.js
3
+ * @description Public company directory + public company page (the Enterprise discovery surface).
4
+ * Anyone (even anonymous) can browse every company on the node (GET /v1/orgs/directory), open one
5
+ * to see its public profile (name, Y-tunnus, description) and catalogue (GET
6
+ * /v1/orgs/:owner/:slug/offerings — the SAME offer cards as profile>offers), and order an offering
7
+ * (POST .../use — ordering requires login). Backend stays protocol-only; the EE module provides the
8
+ * org APIs (a Community node returns ENTERPRISE_REQUIRED).
9
+ * @structure CompaniesView · directory list · PublicOfferingCard (order box, reuses OfferCardView)
10
+ * @usage routed at /v1/companies; deep-link a company via ?c=owner/slug.
11
+ * @version-history
12
+ * v0.1.0 — 2026-06-23 — initial: directory + public company page + order
13
+ */
14
+ import { h } from 'preact';
15
+ import { useState, useEffect } from 'preact/hooks';
16
+ import htm from 'htm';
17
+ import { t } from '/js/i18n.js';
18
+ import { apiGet, apiPost } from '/js/api.js';
19
+ import { DeliverableBody } from '/components/ImageDeliverable.js';
20
+ import { OfferCardView } from '/components/offer-card-view.js';
21
+
22
+ const html = htm.bind(h);
23
+
24
+ const isLoggedIn = () => !!(typeof window !== 'undefined' && window.AIMEAT?.auth?.getSession?.());
25
+
26
+ /** One orderable offering on the public company page (same card as profile>offers + an order box). */
27
+ function PublicOfferingCard({ o, owner, slug }) {
28
+ const offer = o.offer;
29
+ const [input, setInput] = useState('');
30
+ const [busy, setBusy] = useState(false);
31
+ const [result, setResult] = useState(null);
32
+ const [err, setErr] = useState('');
33
+ const loggedIn = isLoggedIn();
34
+
35
+ async function order() {
36
+ setBusy(true); setErr(''); setResult(null);
37
+ try {
38
+ const res = await apiPost(`/v1/orgs/${encodeURIComponent(owner)}/${encodeURIComponent(slug)}/use`,
39
+ { agentName: o.agentName, offerId: o.offerId, input: input.trim() || undefined });
40
+ setResult(res.data); setInput('');
41
+ } catch (e) { setErr(e.message || 'Order failed'); }
42
+ finally { setBusy(false); }
43
+ }
44
+
45
+ const actions = html`
46
+ <div class="cm-not-callable">${o.callable ? t('companies.callable') : t('companies.viaInbox')}</div>
47
+ ${loggedIn
48
+ ? html`
49
+ <textarea class="input-field input-sm of-input" rows="2" value=${input}
50
+ placeholder=${t('companies.usePlaceholder')} onInput=${(e) => setInput(e.target.value)}></textarea>
51
+ <div class="flex-row-wrap">
52
+ <button class="btn-primary btn-sm" disabled=${busy} onClick=${order}>
53
+ ${t('companies.order')}${offer.price?.morsels ? ` · ${offer.price.morsels} ${t('companies.morsels')}` : ''}
54
+ </button>
55
+ </div>`
56
+ : html`<a class="btn-outline btn-sm" href="/v1/profile">${t('companies.loginToOrder')}</a>`}
57
+ ${err && html`<p class="cm-error">${err}</p>`}
58
+ ${result && html`
59
+ <div class="cm-result">
60
+ ${result.kind === 'result'
61
+ ? html`<div class="cm-result-msg">${t('companies.orderResult')}</div><${DeliverableBody} value=${result.result} alt=${offer.title} format=${offer.deliverable?.format} />`
62
+ : html`<div class="cm-result-msg">${t('companies.orderTask').replace('{agent}', o.agentName)}</div>`}
63
+ ${result.receipt?.charged > 0 && html`<div class="cm-receipt">${t('companies.charged').replace('{n}', result.receipt.charged)}</div>`}
64
+ </div>`}
65
+ `;
66
+ return html`<li class="cm-cat-item"><${OfferCardView} entry=${{ agent: o.agentName, online: o.online }} offer=${offer} actions=${actions} /></li>`;
67
+ }
68
+
69
+ /** One purchase receipt in the buyer's "My orders" panel. */
70
+ function ReceiptRow({ o }) {
71
+ const statusKey = o.kind === 'result' ? 'stInstant' : ({ queued: 'stQueued', active: 'stActive', done: 'stDone', failed: 'stFailed' }[o.status] || 'stQueued');
72
+ return html`
73
+ <li class="cm-receipt-row">
74
+ <div class="cm-receipt-top">
75
+ <span class="cm-receipt-title">${o.offerTitle}</span>
76
+ <span class="cm-receipt-amt">${o.charged > 0 ? `${o.charged} ${t('companies.morsels')}` : t('companies.free')}</span>
77
+ </div>
78
+ <div class="cm-receipt-meta">
79
+ <span>${t('companies.fromCompany')}: ${o.orgSlug}</span>
80
+ <span>🤖 ${o.agentName}</span>
81
+ <span>${t('companies.' + statusKey)}</span>
82
+ <span class="cm-mini">${new Date(o.createdAt).toLocaleString()}</span>
83
+ </div>
84
+ ${o.trackingCode && html`<div class="cm-mini cm-receipt-code">🧾 ${o.trackingCode}</div>`}
85
+ </li>`;
86
+ }
87
+
88
+ export default function CompaniesView() {
89
+ const [companies, setCompanies] = useState(null);
90
+ const [selected, setSelected] = useState(null); // { owner, slug }
91
+ const [profile, setProfile] = useState(null); // { org, offerings }
92
+ const [myOrders, setMyOrders] = useState(null);
93
+ const [showOrders, setShowOrders] = useState(false);
94
+ const [err, setErr] = useState('');
95
+ const loggedIn = isLoggedIn();
96
+
97
+ async function loadMyOrders() {
98
+ try { const r = await apiGet('/v1/orders'); setMyOrders(r.data?.orders ?? []); }
99
+ catch { setMyOrders([]); }
100
+ }
101
+ function toggleOrders() { const n = !showOrders; setShowOrders(n); if (n) loadMyOrders(); }
102
+
103
+ async function loadDirectory() {
104
+ try { const r = await apiGet('/v1/orgs/directory'); setCompanies(r.data?.companies ?? []); }
105
+ catch (e) {
106
+ if (e.code === 'ENTERPRISE_REQUIRED') setCompanies('enterprise');
107
+ else { setErr(e.message || 'Failed to load'); setCompanies([]); }
108
+ }
109
+ }
110
+ useEffect(() => { loadDirectory(); }, []);
111
+
112
+ async function openCompany(owner, slug) {
113
+ setSelected({ owner, slug }); setProfile(null); setErr('');
114
+ try { history.replaceState(null, '', `/v1/companies?c=${encodeURIComponent(owner)}/${encodeURIComponent(slug)}`); } catch { /* ignore */ }
115
+ try {
116
+ const r = await apiGet(`/v1/orgs/${encodeURIComponent(owner)}/${encodeURIComponent(slug)}/offerings`);
117
+ setProfile(r.data ?? null);
118
+ } catch (e) { setErr(e.message || ''); }
119
+ }
120
+
121
+ // Deep-link: once the directory is loaded, open ?c=owner/slug if present.
122
+ useEffect(() => {
123
+ if (Array.isArray(companies) && !selected) {
124
+ const c = new URLSearchParams(location.search).get('c');
125
+ if (c && c.includes('/')) { const [o, s] = c.split('/'); openCompany(o, s); }
126
+ }
127
+ }, [companies]);
128
+
129
+ if (companies === null) return html`<div class="cm-container"><div class="cm-center"><div class="spinner"></div></div></div>`;
130
+ if (companies === 'enterprise') return html`<div class="cm-container"><div class="cm-card"><h1 class="cm-title">${t('companies.title')}</h1><p class="cm-desc">${t('companies.enterpriseRequired')}</p></div></div>`;
131
+
132
+ return html`
133
+ <div class="cm-container">
134
+ <header class="cm-header">
135
+ <div class="cm-header-row">
136
+ <h1 class="cm-title">${t('companies.title')}</h1>
137
+ ${loggedIn && html`<button class="btn-outline btn-sm" onClick=${toggleOrders}>${showOrders ? t('companies.done') : t('companies.myOrders')}</button>`}
138
+ </div>
139
+ <p class="cm-desc">${t('companies.desc')}</p>
140
+ </header>
141
+ ${err && html`<p class="cm-error">${err}</p>`}
142
+
143
+ ${showOrders && html`
144
+ <div class="cm-col cm-orders-panel">
145
+ <h2 class="cm-col-title">${t('companies.myOrders')}</h2>
146
+ ${myOrders === null ? html`<div class="cm-center"><div class="spinner"></div></div>`
147
+ : (myOrders.length === 0 ? html`<p class="cm-empty">${t('companies.noMyOrders')}</p>`
148
+ : html`<ul class="cm-receipt-list">${myOrders.map(o => html`<${ReceiptRow} key=${o.id} o=${o} />`)}</ul>`)}
149
+ </div>`}
150
+ <div class="cm-grid">
151
+ <aside class="cm-col">
152
+ <h2 class="cm-col-title">${t('companies.directory')} (${companies.length})</h2>
153
+ ${companies.length === 0 && html`<p class="cm-empty">${t('companies.none')}</p>`}
154
+ <ul class="cm-dir-list">
155
+ ${companies.map(c => html`<li key=${c.goii}>
156
+ <button class="cm-dir-item ${selected?.slug === c.slug && selected?.owner === c.creatorOwner ? 'active' : ''}" onClick=${() => openCompany(c.creatorOwner, c.slug)}>
157
+ ${c.logo && html`<img class="cm-dir-logo" src=${c.logo} alt=${c.name} />`}
158
+ <div class="cm-dir-text">
159
+ <span class="cm-dir-name">${c.name}</span>
160
+ <span class="cm-dir-meta">${c.businessId ? html`<span class="cm-tag">Y ${c.businessId}</span>` : ''}${c.offerings} ${t('companies.offeringsShort')}</span>
161
+ </div>
162
+ </button></li>`)}
163
+ </ul>
164
+ </aside>
165
+ <section class="cm-col">
166
+ ${!selected && html`<p class="cm-empty">${t('companies.selectHint')}</p>`}
167
+ ${selected && !profile && html`<div class="cm-center"><div class="spinner"></div></div>`}
168
+ ${profile && html`
169
+ <div class="cm-profile-head">
170
+ <div class="cm-profile-id">
171
+ ${profile.org?.logo && html`<img class="cm-profile-logo" src=${profile.org.logo} alt=${profile.org?.name} />`}
172
+ <div>
173
+ <h2 class="cm-col-title">${profile.org?.name}</h2>
174
+ ${profile.org?.businessId && html`<span class="cm-mini">${t('companies.businessId')}: ${profile.org.businessId}</span>`}
175
+ </div>
176
+ </div>
177
+ ${profile.org?.description && html`<p class="cm-profile-desc">${profile.org.description}</p>`}
178
+ ${profile.org?.portfolio?.enabled && profile.org.portfolio.publicUrl && html`
179
+ <a class="btn-outline btn-sm cm-pf-link" href=${profile.org.portfolio.publicUrl} target="_blank">${t('companies.viewPortfolio')}</a>`}
180
+ </div>
181
+ <h3 class="cm-form-title">${t('companies.catalogue')}</h3>
182
+ ${(profile.offerings ?? []).length === 0 && html`<p class="cm-empty">${t('companies.noOfferings')}</p>`}
183
+ <ul class="cm-cat-list">
184
+ ${(profile.offerings ?? []).map(o => html`<${PublicOfferingCard} key=${o.agentName + '/' + o.offerId} o=${o} owner=${selected.owner} slug=${selected.slug} />`)}
185
+ </ul>
186
+ `}
187
+ </section>
188
+ </div>
189
+ </div>`;
190
+ }