aimeat 1.23.1 → 1.23.3
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/dist/locales/en.json +674 -125
- package/dist/locales/fi.json +712 -163
- package/dist/public/components/ContactCard.js +28 -0
- package/dist/public/css/theme.css +25 -10
- package/dist/public/css/views/admin.css +12 -0
- package/dist/public/css/views/agents-detail.css +62 -10
- package/dist/public/css/views/landing.css +87 -0
- package/dist/public/css/views/offers.css +96 -0
- package/dist/public/css/views/portfolio.css +18 -0
- package/dist/public/css/views/profile.css +216 -7
- package/dist/public/css/views/start.css +96 -0
- package/dist/public/img/business-hero.png +0 -0
- package/dist/public/img/toimitus.png +0 -0
- package/dist/public/js/components/tag-cloud.js +46 -31
- package/dist/public/js/components/tag-editor.js +2 -2
- package/dist/public/js/recents.js +40 -0
- package/dist/public/js/services/admin.js +6 -0
- package/dist/public/js/services/agents.js +34 -0
- package/dist/public/js/services/offers.js +152 -0
- package/dist/public/js/services/organisms.js +28 -5
- package/dist/public/js/services/packages.js +1 -0
- package/dist/public/spa.html +32 -2
- package/dist/public/views/admin/subdomains-tab.js +182 -0
- package/dist/public/views/admin.js +2 -0
- package/dist/public/views/business.js +119 -0
- package/dist/public/views/how-it-works.js +159 -0
- package/dist/public/views/landing.js +235 -0
- package/dist/public/views/portfolio.js +120 -23
- package/dist/public/views/pricing.js +75 -0
- package/dist/public/views/profile/access-tab.js +124 -51
- package/dist/public/views/profile/agents/agent-card.js +68 -37
- package/dist/public/views/profile/agents/shared-board.js +56 -22
- package/dist/public/views/profile/agents/tab-activity.js +24 -12
- package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
- package/dist/public/views/profile/agents/tab-data-access.js +9 -13
- package/dist/public/views/profile/agents/tab-integration.js +27 -1
- package/dist/public/views/profile/agents/tab-messages.js +6 -11
- package/dist/public/views/profile/agents/tab-quality.js +45 -5
- package/dist/public/views/profile/agents-tab.js +41 -6
- package/dist/public/views/profile/apps-tab.js +7 -1
- package/dist/public/views/profile/knowledge-tab.js +4 -1
- package/dist/public/views/profile/landing-page.js +427 -123
- package/dist/public/views/profile/memory-tab.js +409 -211
- package/dist/public/views/profile/nodes-tab.js +18 -3
- package/dist/public/views/profile/offers-tab.js +310 -0
- package/dist/public/views/profile/organisms-tab.js +1330 -485
- package/dist/public/views/profile/packages-tab.js +152 -16
- package/dist/public/views/profile/portfolio-tab.js +98 -31
- package/dist/public/views/profile.js +2 -0
- package/dist/public/views/start-flows.js +281 -0
- package/dist/public/views/start.js +258 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +20 -3
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call.js +20 -1
- package/dist/src/cli/connect/tool-call.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +856 -17
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +15 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
- package/dist/src/generated/prisma-postgres/index.js +15 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
- package/dist/src/generated/prisma-postgres/wasm.js +15 -3
- package/dist/src/mcp/organisms.d.ts +3 -0
- package/dist/src/mcp/organisms.d.ts.map +1 -1
- package/dist/src/mcp/organisms.js +18 -0
- package/dist/src/mcp/organisms.js.map +1 -1
- package/dist/src/middleware/subdomain.d.ts +28 -0
- package/dist/src/middleware/subdomain.d.ts.map +1 -0
- package/dist/src/middleware/subdomain.js +32 -0
- package/dist/src/middleware/subdomain.js.map +1 -0
- package/dist/src/models/offer-schemas.d.ts +229 -0
- package/dist/src/models/offer-schemas.d.ts.map +1 -0
- package/dist/src/models/offer-schemas.js +100 -0
- package/dist/src/models/offer-schemas.js.map +1 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +33 -0
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +146 -1
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/apps-backup.d.ts +20 -0
- package/dist/src/routes/apps-backup.d.ts.map +1 -0
- package/dist/src/routes/apps-backup.js +165 -0
- package/dist/src/routes/apps-backup.js.map +1 -0
- package/dist/src/routes/ghii.d.ts.map +1 -1
- package/dist/src/routes/ghii.js +4 -0
- package/dist/src/routes/ghii.js.map +1 -1
- package/dist/src/routes/instances.d.ts +2 -1
- package/dist/src/routes/instances.d.ts.map +1 -1
- package/dist/src/routes/instances.js +59 -1
- package/dist/src/routes/instances.js.map +1 -1
- package/dist/src/routes/organisms.d.ts.map +1 -1
- package/dist/src/routes/organisms.js +27 -5
- package/dist/src/routes/organisms.js.map +1 -1
- package/dist/src/routes/packages.d.ts.map +1 -1
- package/dist/src/routes/packages.js +32 -0
- package/dist/src/routes/packages.js.map +1 -1
- package/dist/src/routes/permissions.d.ts +12 -0
- package/dist/src/routes/permissions.d.ts.map +1 -1
- package/dist/src/routes/permissions.js +35 -3
- package/dist/src/routes/permissions.js.map +1 -1
- package/dist/src/routes/portal.d.ts +3 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +12 -0
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/public-stats.d.ts +15 -0
- package/dist/src/routes/public-stats.d.ts.map +1 -0
- package/dist/src/routes/public-stats.js +90 -0
- package/dist/src/routes/public-stats.js.map +1 -0
- package/dist/src/routes/sse.d.ts +7 -2
- package/dist/src/routes/sse.d.ts.map +1 -1
- package/dist/src/routes/sse.js +35 -5
- package/dist/src/routes/sse.js.map +1 -1
- package/dist/src/routes/subdomains.d.ts +33 -0
- package/dist/src/routes/subdomains.d.ts.map +1 -0
- package/dist/src/routes/subdomains.js +208 -0
- package/dist/src/routes/subdomains.js.map +1 -0
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +12 -1
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/apps-backup-export.d.ts +22 -0
- package/dist/src/services/apps-backup-export.d.ts.map +1 -0
- package/dist/src/services/apps-backup-export.js +136 -0
- package/dist/src/services/apps-backup-export.js.map +1 -0
- package/dist/src/services/apps-backup-import.d.ts +120 -0
- package/dist/src/services/apps-backup-import.d.ts.map +1 -0
- package/dist/src/services/apps-backup-import.js +259 -0
- package/dist/src/services/apps-backup-import.js.map +1 -0
- package/dist/src/services/component-registrar.d.ts +17 -0
- package/dist/src/services/component-registrar.d.ts.map +1 -1
- package/dist/src/services/component-registrar.js +63 -5
- package/dist/src/services/component-registrar.js.map +1 -1
- package/dist/src/services/handbooks/appdev.d.ts +1 -1
- package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
- package/dist/src/services/handbooks/appdev.js +5 -2
- package/dist/src/services/handbooks/appdev.js.map +1 -1
- package/dist/src/services/schema-validator.d.ts.map +1 -1
- package/dist/src/services/schema-validator.js +12 -0
- package/dist/src/services/schema-validator.js.map +1 -1
- package/dist/src/storage/interface.d.ts +11 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +10 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +76 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +61 -0
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +11 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/index.d.ts +1 -0
- package/dist/src/storage/repositories/index.d.ts.map +1 -1
- package/dist/src/storage/repositories/package.repository.d.ts +2 -0
- package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
- package/dist/static/app-catalog.html +1139 -28
- package/package.json +1 -1
- package/prisma/schema.postgres.prisma +11 -0
- package/prisma/schema.prisma +11 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file offers.js
|
|
3
|
+
* @description Frontend service for the Agent Offers surface ("what can I do with my agents"). Reads
|
|
4
|
+
* the owner aggregate feed + per-agent offers, and the mode-aware Ask: a schedule-born offer triggers
|
|
5
|
+
* its schedule; a task-runner/autonomous agent runs a task; an interactive/workstation agent gets a
|
|
6
|
+
* paste-ready prompt (AIMEAT's prompt-driven flow). See docs/plans/2026-06-12-agent-offers-*.md.
|
|
7
|
+
* @structure listOffers / getAgentOffers / publishOffers · ask(agentEntry, offer, inputs) · buildAskPrompt
|
|
8
|
+
* @usage import * as offers from '/js/services/offers.js';
|
|
9
|
+
* @version-history
|
|
10
|
+
* v1.0.0 -- 2026-06-12 -- Initial: Do feed + mode-aware Ask (run / copy-prompt / schedule-trigger).
|
|
11
|
+
* v1.1.0 -- 2026-06-12 -- Billable offers: setOfferBilling (patch one offer's price + visibility).
|
|
12
|
+
* v1.2.0 -- 2026-06-12 -- Inbox fixes: deliverable read from the AGENT namespace (prefix + task-tag
|
|
13
|
+
* fallback, returns a string); Ask task title carries the user's request (findable in the queue).
|
|
14
|
+
* v1.3.0 -- 2026-06-12 -- Offer task carries ONLY the user's request (title + description); the offer
|
|
15
|
+
* is referenced via scope.offer_id/offer_title — no more restuffing offer.ask/example (which made
|
|
16
|
+
* the agent treat its own boilerplate as the request).
|
|
17
|
+
*/
|
|
18
|
+
import { apiGet, apiPost, apiPut } from '/js/api.js';
|
|
19
|
+
import { copyToClipboard } from '/js/utils.js';
|
|
20
|
+
import { createTask, rateTask } from '/js/services/agent-tasks.js';
|
|
21
|
+
|
|
22
|
+
/** The goal-first feed: every agent's offers + mode/availability. */
|
|
23
|
+
export async function listOffers() {
|
|
24
|
+
return apiGet('/v1/offers');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One agent's published offers. */
|
|
28
|
+
export async function getAgentOffers(name) {
|
|
29
|
+
return apiGet(`/v1/agents/${encodeURIComponent(name)}/offers`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Publish/replace an agent's offers (agent self or owner). */
|
|
33
|
+
export async function publishOffers(name, doc) {
|
|
34
|
+
return apiPut(`/v1/agents/${encodeURIComponent(name)}/offers`, doc);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Owner edits ONE offer's billing fields (price + visibility), preserving the rest of the doc.
|
|
38
|
+
* Re-reads the agent's current offers first (avoid clobbering a stale feed), patches, re-publishes.
|
|
39
|
+
* `price` is `{ morsels, unit }` or `null` (not for sale); `visibility` is private|unlisted|public. */
|
|
40
|
+
export async function setOfferBilling(name, offerId, { price, visibility }) {
|
|
41
|
+
const cur = await getAgentOffers(name);
|
|
42
|
+
const offers = (cur?.data?.offers || []).map(o => o.id === offerId
|
|
43
|
+
? { ...o, ...(price !== undefined ? { price } : {}), ...(visibility !== undefined ? { visibility } : {}) }
|
|
44
|
+
: o);
|
|
45
|
+
return publishOffers(name, { offers });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The Inbox feed: everything that came back across all agents (non-draft tasks, newest first). */
|
|
49
|
+
export async function listDeliverables() {
|
|
50
|
+
return apiGet('/v1/deliverables');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Fetch a task's deliverable from the AGENT's memory namespace (owner→agent read).
|
|
54
|
+
* Deliverables live under the agent's GAII, not the owner's, so we read via
|
|
55
|
+
* `?agent=<gaii>`. Primary handle is the recorded `deliverableKey`; fallback is the
|
|
56
|
+
* `task:<id>` tag the runner stamps on every write (works even when the agent never
|
|
57
|
+
* set deliverableKey — e.g. CrewAI agents that only publish to memory). Returns the
|
|
58
|
+
* deliverable value as a string, or null if nothing was found. Mirrors the proven
|
|
59
|
+
* read in agents-tasks-subtab.js. */
|
|
60
|
+
export async function getDeliverableContent({ agentGaii, deliverableKey, taskId }) {
|
|
61
|
+
if (!agentGaii) return null;
|
|
62
|
+
const enc = encodeURIComponent;
|
|
63
|
+
const reqs = [];
|
|
64
|
+
if (deliverableKey) reqs.push(apiGet(`/v1/memory?agent=${enc(agentGaii)}&prefix=${enc(deliverableKey)}&per_page=5`).catch(() => null));
|
|
65
|
+
if (taskId) reqs.push(apiGet(`/v1/memory?agent=${enc(agentGaii)}&tags=${enc('task:' + taskId)}&per_page=50`).catch(() => null));
|
|
66
|
+
const results = await Promise.all(reqs);
|
|
67
|
+
const items = [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
for (const r of results) {
|
|
70
|
+
const list = r?.data?.items || r?.data || [];
|
|
71
|
+
for (const it of (Array.isArray(list) ? list : [])) {
|
|
72
|
+
if (it.key && !seen.has(it.key)) { seen.add(it.key); items.push(it); }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!items.length) return null;
|
|
76
|
+
const len = (v) => (typeof v === 'string' ? v.length : 0);
|
|
77
|
+
// Prefer the exact deliverableKey, then an *output key, then the longest text value.
|
|
78
|
+
const pick = items.find(i => i.key === deliverableKey)
|
|
79
|
+
|| items.find(i => /(latest_)?output$/.test(i.key))
|
|
80
|
+
|| items.slice().sort((a, b) => len(b.value) - len(a.value))[0];
|
|
81
|
+
const v = pick?.value;
|
|
82
|
+
if (v == null) return null;
|
|
83
|
+
return typeof v === 'string' ? v : JSON.stringify(v, null, 2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Rate a done task's deliverable via the LOCKED rate endpoint (→ agents.<agent>.statistics.*).
|
|
87
|
+
* Owner ratings bypass source-grounding; we still send source_grounded:true (the owner saw it). */
|
|
88
|
+
export async function rateDeliverable(agent, taskId, { stars, comment, context = 'other' }) {
|
|
89
|
+
return rateTask(agent, taskId, { stars, context, comment, source_grounded: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Compose the paste-ready prompt for an interactive/workstation agent (prompt-driven Ask). */
|
|
93
|
+
export function buildAskPrompt(agentEntry, offer, inputs) {
|
|
94
|
+
const lines = [
|
|
95
|
+
`You are the agent "${agentEntry.agent}". I'm asking you to perform one of your offers.`,
|
|
96
|
+
``,
|
|
97
|
+
`OFFER: ${offer.title}`,
|
|
98
|
+
`WHAT I'M ASKING: ${offer.ask}`,
|
|
99
|
+
];
|
|
100
|
+
if (offer.example) lines.push(`EXAMPLE OF A REQUEST LIKE THIS: ${offer.example}`);
|
|
101
|
+
if (inputs && String(inputs).trim()) lines.push(``, `MY REQUEST: ${String(inputs).trim()}`);
|
|
102
|
+
if (offer.deliverable?.format) lines.push(``, `DELIVER: a ${offer.deliverable.format}${offer.deliverable.location?.space ? ` in ${offer.deliverable.location.space}` : ''}.`);
|
|
103
|
+
lines.push(``, `Do exactly this offer and nothing outside its scope. When done, write the deliverable and tell me where it is.`);
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function scheduleIdOf(offer) {
|
|
108
|
+
const sb = offer?.availability?.scheduleBorn;
|
|
109
|
+
if (!sb) return null;
|
|
110
|
+
return typeof sb === 'object' ? (sb.scheduleId || null) : null; // string form is a human label only
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Mode-aware Ask. Branch order: schedule-born first (independent of mode), then by agent mode.
|
|
115
|
+
* Returns one of:
|
|
116
|
+
* { kind: 'triggered' } — schedule-born → ran the schedule now
|
|
117
|
+
* { kind: 'task', taskId, agent } — task-runner/autonomous → created a queued task
|
|
118
|
+
* { kind: 'prompt', copied: true, prompt } — interactive/workstation → copied a paste-ready prompt
|
|
119
|
+
*/
|
|
120
|
+
export async function ask(agentEntry, offer, inputs) {
|
|
121
|
+
const scheduleId = scheduleIdOf(offer);
|
|
122
|
+
if (scheduleId) {
|
|
123
|
+
const r = await apiPost(`/v1/schedules/${encodeURIComponent(scheduleId)}/trigger`, {});
|
|
124
|
+
return { kind: 'triggered', ok: r?.ok !== false, error: r?.error };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const mode = agentEntry.mode || 'interactive';
|
|
128
|
+
if (mode === 'task-runner' || mode === 'autonomous') {
|
|
129
|
+
// The agent ALREADY owns the offer (it published it). Do NOT restuff offer.ask/example into the
|
|
130
|
+
// task — that makes the agent treat its own boilerplate as the request (e.g. jokes ABOUT four
|
|
131
|
+
// comedians instead of jokes on the topic). The task carries ONLY the user's request as title +
|
|
132
|
+
// description; the offer is referenced structurally via scope (the runtime resolves it by offer_id).
|
|
133
|
+
const reqText = inputs ? String(inputs).trim() : '';
|
|
134
|
+
const title = reqText ? (reqText.length > 80 ? reqText.slice(0, 80) + '…' : reqText) : offer.title;
|
|
135
|
+
const r = await createTask(agentEntry.agent, {
|
|
136
|
+
title,
|
|
137
|
+
description: reqText,
|
|
138
|
+
status: 'queued', // REQUIRED so a task-runner auto-activates it (other modes keep the owner-start gate)
|
|
139
|
+
scope: [
|
|
140
|
+
{ name: 'kind', value: 'offer', type: 'text' },
|
|
141
|
+
{ name: 'offer_id', value: offer.id, type: 'text' },
|
|
142
|
+
{ name: 'offer_title', value: offer.title, type: 'text' },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
return { kind: 'task', taskId: r?.data?.task?.id || r?.data?.id, agent: agentEntry.agent, ok: r?.ok !== false, error: r?.error };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// interactive / workstation → prompt-driven
|
|
149
|
+
const prompt = buildAskPrompt(agentEntry, offer, inputs);
|
|
150
|
+
await copyToClipboard(prompt);
|
|
151
|
+
return { kind: 'prompt', copied: true, prompt };
|
|
152
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* (a "project" is just an organism with a meta.manifest): apply a
|
|
5
|
-
* read the workspace, write/publish drafts, and
|
|
2
|
+
* @file organisms.js
|
|
3
|
+
* @description AIMEAT Organisms Service — organism CRUD, membership, join requests, and the
|
|
4
|
+
* manifest-driven workspace (a "project" is just an organism with a meta.manifest): apply a
|
|
5
|
+
* template, generate a workspace with AI, read the workspace, write/publish drafts, and
|
|
6
|
+
* resolve gate approvals.
|
|
7
|
+
* @usage import * as orgService from '/js/services/organisms.js';
|
|
8
|
+
* @version-history
|
|
9
|
+
* v1.1.0 — 2026-06-10 — Generator prompt: per-property "description", "readOnly": true for
|
|
10
|
+
* agent-filled result fields (never required), "x-default": "currentUser" for requester
|
|
11
|
+
* fields, and a manifest "i18n" block (en + request language) with flat "{ns}.{field}",
|
|
12
|
+
* "{ns}.{field}.hint" and "type.{name}" labels the workspace UI renders.
|
|
6
13
|
*/
|
|
7
14
|
import { api, apiGet, apiPost, apiPut, apiPatch, apiDelete } from '/js/api.js';
|
|
8
15
|
|
|
@@ -235,6 +242,7 @@ Respond with this shape:
|
|
|
235
242
|
"kind": "<short-kebab-case kind, e.g. game-dev>",
|
|
236
243
|
"status": "active",
|
|
237
244
|
"objectTypes": [ <one entry per type> ],
|
|
245
|
+
"i18n": { <UI labels per language — see below> },
|
|
238
246
|
"policy": { "agentAutonomy": "L3", "alwaysGate": [] }
|
|
239
247
|
},
|
|
240
248
|
"schemas": { <one JSON Schema per RECORDS type, keyed by that type's namespace> },
|
|
@@ -254,6 +262,17 @@ Give every objectType all of these fields:
|
|
|
254
262
|
For each RECORDS type, add a JSON Schema under "schemas" keyed by its namespace:
|
|
255
263
|
{ "type": "object", "required": ["id", ...], "properties": { "id": { "type": "string" }, ... } }
|
|
256
264
|
Give every record an "id" string property. Use "enum" for status-like fields and "format": "date" (or "date-time") for date fields.
|
|
265
|
+
Also, for each property:
|
|
266
|
+
- Give every property except "id" a short English "description" telling a human what to fill in.
|
|
267
|
+
- Result fields that an agent or automation fills later (e.g. result_ref, output links, error details) get "readOnly": true — the form hides them from humans. Never list a readOnly field in "required".
|
|
268
|
+
- Fields that should default to the acting user's identity (e.g. requested_by, created_by, author) get "x-default": "currentUser".
|
|
269
|
+
|
|
270
|
+
Inside "manifest", add an "i18n" object with UI labels: one entry per language — always "en", plus the language of the user's request if different. Keys are flat strings:
|
|
271
|
+
- "<namespace>.<field>": a short human label for that field
|
|
272
|
+
- "<namespace>.<field>.hint": one short sentence telling the user what to enter
|
|
273
|
+
- "type.<objectType name>": a short plural label for that type's tab
|
|
274
|
+
Example: "i18n": { "en": { "shared.milestones.title": "Title", "shared.milestones.title.hint": "A short name for the milestone", "type.milestone": "Milestones" }, "fi": { "shared.milestones.title": "Otsikko", "shared.milestones.title.hint": "Lyhyt nimi virstanpylväälle", "type.milestone": "Virstanpylväät" } }
|
|
275
|
+
Cover every field of every RECORDS type (labels + hints) and every objectType (type.<name>) in every language you include.
|
|
257
276
|
|
|
258
277
|
For each DOCUMENT type, the content is free markdown — keep it out of "schemas" and "examples" (a document needs neither).
|
|
259
278
|
|
|
@@ -267,10 +286,14 @@ Worked example — request "track game milestones plus free-form design docs":
|
|
|
267
286
|
{ "name": "milestone", "schemaRef": "schema:milestone@1", "namespace": "shared.milestones", "backing": "memory", "writeRole": "member", "cardinality": "many", "versioned": true, "mode": "records" },
|
|
268
287
|
{ "name": "design-doc", "schemaRef": "schema:design-doc@1", "namespace": "shared.design-docs", "backing": "memory", "writeRole": "member", "cardinality": "many", "versioned": true, "mode": "document" }
|
|
269
288
|
],
|
|
289
|
+
"i18n": {
|
|
290
|
+
"en": { "type.milestone": "Milestones", "type.design-doc": "Design docs", "shared.milestones.title": "Title", "shared.milestones.title.hint": "A short name for the milestone", "shared.milestones.status": "Status", "shared.milestones.status.hint": "Where this milestone stands", "shared.milestones.due_date": "Due date", "shared.milestones.due_date.hint": "When this should be done" },
|
|
291
|
+
"fi": { "type.milestone": "Virstanpylväät", "type.design-doc": "Suunnitteludokumentit", "shared.milestones.title": "Otsikko", "shared.milestones.title.hint": "Lyhyt nimi virstanpylväälle", "shared.milestones.status": "Tila", "shared.milestones.status.hint": "Missä vaiheessa tämä on", "shared.milestones.due_date": "Määräpäivä", "shared.milestones.due_date.hint": "Milloin tämän pitäisi olla valmis" }
|
|
292
|
+
},
|
|
270
293
|
"policy": { "agentAutonomy": "L3", "alwaysGate": [] }
|
|
271
294
|
},
|
|
272
295
|
"schemas": {
|
|
273
|
-
"shared.milestones": { "type": "object", "required": ["id", "title", "status"], "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "status": { "type": "string", "enum": ["planned", "in-progress", "done"] }, "due_date": { "type": "string", "format": "date" } } }
|
|
296
|
+
"shared.milestones": { "type": "object", "required": ["id", "title", "status"], "properties": { "id": { "type": "string" }, "title": { "type": "string", "description": "A short name for the milestone" }, "status": { "type": "string", "enum": ["planned", "in-progress", "done"], "description": "Where this milestone stands" }, "due_date": { "type": "string", "format": "date", "description": "When this should be done" } } }
|
|
274
297
|
},
|
|
275
298
|
"examples": {
|
|
276
299
|
"shared.milestones": [ { "id": "example-1", "title": "Vertical slice", "status": "planned", "due_date": "2026-09-01" } ]
|
|
@@ -27,6 +27,7 @@ export const createPackage = (data) => apiPost('/v1/packages', data);
|
|
|
27
27
|
export const createVersion = (groupId, data) => apiPost(`/v1/packages/${enc(groupId)}/versions`, data);
|
|
28
28
|
export const updatePackage = (groupId, data) => apiPatch(`/v1/packages/${enc(groupId)}`, data);
|
|
29
29
|
export const archiveVersion = (groupId, version) => apiDelete(`/v1/packages/${enc(groupId)}/versions/${enc(version)}`);
|
|
30
|
+
export const archivePackageGroup = (groupId) => apiDelete(`/v1/packages/${enc(groupId)}`);
|
|
30
31
|
|
|
31
32
|
// ── Instances ──
|
|
32
33
|
export const listInstances = (params) => apiGet('/v1/instances' + buildQuery(params));
|
package/dist/public/spa.html
CHANGED
|
@@ -67,9 +67,11 @@
|
|
|
67
67
|
<!-- View CSS preloaded so they are ready before any view renders (prevents FOUC) -->
|
|
68
68
|
<!-- hrefs are stamped with ?v=BUILD_ID at serve time by portal.ts serveSpa() -->
|
|
69
69
|
<link rel="stylesheet" href="/css/views/portal.css">
|
|
70
|
+
<link rel="stylesheet" href="/css/views/landing.css">
|
|
70
71
|
<link rel="stylesheet" href="/css/views/portal-classic.css">
|
|
71
72
|
<link rel="stylesheet" href="/css/views/portal-dev.css">
|
|
72
73
|
<link rel="stylesheet" href="/css/views/profile.css">
|
|
74
|
+
<link rel="stylesheet" href="/css/views/offers.css">
|
|
73
75
|
<link rel="stylesheet" href="/css/views/foundry.css">
|
|
74
76
|
<link rel="stylesheet" href="/css/views/calibrator.css">
|
|
75
77
|
<link rel="stylesheet" href="/css/views/hobbies.css">
|
|
@@ -86,6 +88,7 @@
|
|
|
86
88
|
<link rel="stylesheet" href="/css/views/admin-agent-integration.css">
|
|
87
89
|
<link rel="stylesheet" href="/css/views/public-knowledge-viewer.css">
|
|
88
90
|
<link rel="stylesheet" href="/css/views/public-workspace-viewer.css">
|
|
91
|
+
<link rel="stylesheet" href="/css/views/start.css">
|
|
89
92
|
<script type="importmap">
|
|
90
93
|
{
|
|
91
94
|
"imports": {
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
"/js/format.js": "/js/format.js",
|
|
99
102
|
"/js/api.js": "/js/api.js",
|
|
100
103
|
"/js/hooks.js": "/js/hooks.js",
|
|
104
|
+
"/js/recents.js": "/js/recents.js",
|
|
101
105
|
"/js/services/admin.js": "/js/services/admin.js",
|
|
102
106
|
"/js/services/agents.js": "/js/services/agents.js",
|
|
103
107
|
"/js/services/apps.js": "/js/services/apps.js",
|
|
@@ -151,6 +155,7 @@
|
|
|
151
155
|
"/js/services/memory.js": "/js/services/memory.js",
|
|
152
156
|
"/js/services/nodes.js": "/js/services/nodes.js",
|
|
153
157
|
"/js/services/organisms.js": "/js/services/organisms.js",
|
|
158
|
+
"/js/services/offers.js": "/js/services/offers.js",
|
|
154
159
|
"/js/services/packages.js": "/js/services/packages.js",
|
|
155
160
|
"/js/services/security.js": "/js/services/security.js",
|
|
156
161
|
"/js/services/stats.js": "/js/services/stats.js",
|
|
@@ -165,6 +170,7 @@
|
|
|
165
170
|
"/components/Mermaid.js": "/components/Mermaid.js",
|
|
166
171
|
"/components/NotificationBell.js": "/components/NotificationBell.js",
|
|
167
172
|
"/components/CopyButton.js": "/components/CopyButton.js",
|
|
173
|
+
"/components/ContactCard.js": "/components/ContactCard.js",
|
|
168
174
|
"/components/Spinner.js": "/components/Spinner.js",
|
|
169
175
|
"/components/EmptyState.js": "/components/EmptyState.js",
|
|
170
176
|
"/components/ToggleSwitch.js": "/components/ToggleSwitch.js",
|
|
@@ -255,7 +261,14 @@
|
|
|
255
261
|
// insufficient — modules stay in the module registry for the full session).
|
|
256
262
|
const B = window.__B || '';
|
|
257
263
|
const ROUTES = {
|
|
258
|
-
|
|
264
|
+
// Landing/portal split (2026-06-10): the route serves the logged-out landing;
|
|
265
|
+
// logged-in visitors are forwarded to /v1/profile by the view itself. The old
|
|
266
|
+
// portal view stays reachable at /v1/classic (prompt boxes / "Build").
|
|
267
|
+
'/v1/portal': () => import('/views/landing.js' + B),
|
|
268
|
+
'/v1/pricing': () => import('/views/pricing.js' + B),
|
|
269
|
+
'/v1/start': () => import('/views/start.js' + B),
|
|
270
|
+
'/v1/how-it-works': () => import('/views/how-it-works.js' + B),
|
|
271
|
+
'/v1/business': () => import('/views/business.js' + B),
|
|
259
272
|
'/v1/profile': () => import('/views/profile.js' + B),
|
|
260
273
|
'/v1/guides': () => import('/views/guides.js' + B),
|
|
261
274
|
'/v1/hobbies': () => import('/views/hobbies.js' + B),
|
|
@@ -347,6 +360,14 @@
|
|
|
347
360
|
// Only intercept internal /v1/ links
|
|
348
361
|
if (!href || !href.startsWith('/v1/')) return;
|
|
349
362
|
if (e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
363
|
+
// Respect explicit new-tab / new-window targets — without this, an
|
|
364
|
+
// <a href="/v1/apps/.../launch?mode=inline" target="_blank"> from a
|
|
365
|
+
// child component would be hijacked, navigated in the same tab via
|
|
366
|
+
// matchRoute (which doesn't know /v1/apps/...), and dump the user
|
|
367
|
+
// back on the landing page.
|
|
368
|
+
const target = a.getAttribute('target');
|
|
369
|
+
if (target && target !== '_self' && target !== '') return;
|
|
370
|
+
if (a.hasAttribute('download')) return;
|
|
350
371
|
e.preventDefault();
|
|
351
372
|
navigate(href);
|
|
352
373
|
};
|
|
@@ -438,6 +459,10 @@
|
|
|
438
459
|
|
|
439
460
|
const handleNav = (e, path) => {
|
|
440
461
|
e.preventDefault();
|
|
462
|
+
// Mark in-app navigation: the landing redirects logged-in users to their Home
|
|
463
|
+
// ONLY on direct arrival (bookmark/external link), never when they deliberately
|
|
464
|
+
// navigated here from inside the app (e.g. the brand link).
|
|
465
|
+
try { sessionStorage.setItem('aimeat.in-app', '1'); } catch {}
|
|
441
466
|
navigate(path);
|
|
442
467
|
};
|
|
443
468
|
|
|
@@ -456,7 +481,12 @@
|
|
|
456
481
|
<button class="topnav-burger" aria-label="Menu" aria-expanded=${menuOpen ? 'true' : 'false'}
|
|
457
482
|
onClick=${() => setMenuOpen(o => !o)}>☰</button>
|
|
458
483
|
<div class="topnav-menu ${menuOpen ? 'open' : ''}">
|
|
459
|
-
|
|
484
|
+
<!-- "Try it" leads to the landing's path cards + app gallery — an empty
|
|
485
|
+
Welcome Board on /v1/classic was the weakest possible first experience.
|
|
486
|
+
Classic stays reachable deeper (landing card B "Start building"). -->
|
|
487
|
+
<a href="/v1/portal" onClick=${(e) => { handleNav(e, '/v1/portal'); setMenuOpen(false); }} class="${currentPath === '/v1/portal' ? 'active' : ''}">${t('nav.try')}</a>
|
|
488
|
+
<a href="/v1/how-it-works" onClick=${(e) => { handleNav(e, '/v1/how-it-works'); setMenuOpen(false); }} class="${currentPath === '/v1/how-it-works' ? 'active' : ''}">${t('nav.howItWorks') || 'How it works'}</a>
|
|
489
|
+
<a href="/v1/business" onClick=${(e) => { handleNav(e, '/v1/business'); setMenuOpen(false); }} class="${currentPath === '/v1/business' ? 'active' : ''}">${t('nav.business') || 'For your business'}</a>
|
|
460
490
|
<a href="/v1/portal?view=dev" onClick=${(e) => { handleNav(e, '/v1/portal?view=dev'); setMenuOpen(false); }}>${t('nav.devView')}</a>
|
|
461
491
|
<a href="/v1/help" onClick=${(e) => { handleNav(e, '/v1/help'); setMenuOpen(false); }} class="${currentPath === '/v1/help' ? 'active' : ''}">${t('nav.help')}</a>
|
|
462
492
|
${session && html`
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file subdomains-tab.js
|
|
3
|
+
* @description Admin dashboard Subdomains tab — operator management of
|
|
4
|
+
* subdomain → site mappings (serve a published app at `<sub>.<apex>` or
|
|
5
|
+
* 301-redirect). Table with enabled-toggle, create form, and
|
|
6
|
+
* type-to-confirm deletion.
|
|
7
|
+
* @structure SubdomainsAdminTab (default)
|
|
8
|
+
* @usage Mounted by the admin dashboard tab router (views/admin.js).
|
|
9
|
+
* @version-history
|
|
10
|
+
* v1.0.0 — 2026-06-12 — Initial: subdomain routing (operator-only management)
|
|
11
|
+
*/
|
|
12
|
+
import { h } from 'preact';
|
|
13
|
+
import { useState, useEffect, useCallback } from 'preact/hooks';
|
|
14
|
+
import htm from 'htm';
|
|
15
|
+
const html = htm.bind(h);
|
|
16
|
+
import { t } from '/js/i18n.js';
|
|
17
|
+
import { dt, Spinner, Empty, ErrorBox } from './shared.js';
|
|
18
|
+
import { Modal } from '/components/Modal.js';
|
|
19
|
+
import * as adminService from '/js/services/admin.js';
|
|
20
|
+
|
|
21
|
+
export default function SubdomainsAdminTab() {
|
|
22
|
+
const [sites, setSites] = useState([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
26
|
+
const [form, setForm] = useState({ subdomain: '', kind: 'app', target: '' });
|
|
27
|
+
const [formError, setFormError] = useState(null);
|
|
28
|
+
const [saving, setSaving] = useState(false);
|
|
29
|
+
// Type-to-confirm delete state: { subdomain, typed }
|
|
30
|
+
const [deleting, setDeleting] = useState(null);
|
|
31
|
+
|
|
32
|
+
const load = useCallback(async () => {
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const resp = await adminService.getSubdomainSites();
|
|
37
|
+
setSites(resp?.data?.sites || []);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setError(err?.message || String(err));
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => { load(); }, [load]);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const handler = () => { load(); };
|
|
49
|
+
window.addEventListener('aimeat-live-update', handler);
|
|
50
|
+
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
51
|
+
}, [load]);
|
|
52
|
+
|
|
53
|
+
const create = useCallback(async () => {
|
|
54
|
+
setFormError(null);
|
|
55
|
+
setSaving(true);
|
|
56
|
+
try {
|
|
57
|
+
await adminService.createSubdomainSite({
|
|
58
|
+
subdomain: form.subdomain.trim().toLowerCase(),
|
|
59
|
+
kind: form.kind,
|
|
60
|
+
target: form.target.trim(),
|
|
61
|
+
});
|
|
62
|
+
setShowCreate(false);
|
|
63
|
+
setForm({ subdomain: '', kind: 'app', target: '' });
|
|
64
|
+
load();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setFormError(err?.message || String(err));
|
|
67
|
+
} finally {
|
|
68
|
+
setSaving(false);
|
|
69
|
+
}
|
|
70
|
+
}, [form, load]);
|
|
71
|
+
|
|
72
|
+
const toggleEnabled = useCallback(async (site) => {
|
|
73
|
+
try {
|
|
74
|
+
await adminService.updateSubdomainSite(site.subdomain, { enabled: !site.enabled });
|
|
75
|
+
load();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
setError(err?.message || String(err));
|
|
78
|
+
}
|
|
79
|
+
}, [load]);
|
|
80
|
+
|
|
81
|
+
const doDelete = useCallback(async () => {
|
|
82
|
+
if (!deleting || deleting.typed !== deleting.subdomain) return;
|
|
83
|
+
try {
|
|
84
|
+
await adminService.deleteSubdomainSite(deleting.subdomain);
|
|
85
|
+
setDeleting(null);
|
|
86
|
+
load();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
setError(err?.message || String(err));
|
|
89
|
+
setDeleting(null);
|
|
90
|
+
}
|
|
91
|
+
}, [deleting, load]);
|
|
92
|
+
|
|
93
|
+
// Public URL for a mapping, derived from the page's own host
|
|
94
|
+
const siteUrl = (sub) => `${location.protocol}//${sub}.${location.host}/`;
|
|
95
|
+
|
|
96
|
+
return html`
|
|
97
|
+
<p class="adm-text-sm adm-text-dim adm-mb-md">${t('admin.subdomains.desc')}</p>
|
|
98
|
+
|
|
99
|
+
<div class="adm-card">
|
|
100
|
+
<div class="adm-flex-between adm-mb-md">
|
|
101
|
+
<h2>${t('admin.subdomains.title')}</h2>
|
|
102
|
+
<button class="adm-btn-sm" onClick=${() => { setShowCreate(!showCreate); setFormError(null); }}>
|
|
103
|
+
${showCreate ? t('common.cancel') : '+ ' + t('admin.subdomains.add')}
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
${showCreate && html`
|
|
108
|
+
<div class="adm-subdomain-form adm-mb-md">
|
|
109
|
+
<input class="adm-input" placeholder=${t('admin.subdomains.subdomain')}
|
|
110
|
+
value=${form.subdomain}
|
|
111
|
+
onInput=${e => setForm({ ...form, subdomain: e.target.value })} />
|
|
112
|
+
<select class="adm-input" value=${form.kind}
|
|
113
|
+
onChange=${e => setForm({ ...form, kind: e.target.value })}>
|
|
114
|
+
<option value="app">${t('admin.subdomains.kindApp')}</option>
|
|
115
|
+
<option value="redirect">${t('admin.subdomains.kindRedirect')}</option>
|
|
116
|
+
</select>
|
|
117
|
+
<input class="adm-input" value=${form.target}
|
|
118
|
+
placeholder=${form.kind === 'app' ? t('admin.subdomains.targetHintApp') : t('admin.subdomains.targetHintRedirect')}
|
|
119
|
+
onInput=${e => setForm({ ...form, target: e.target.value })} />
|
|
120
|
+
<button class="adm-btn" disabled=${saving || !form.subdomain.trim() || !form.target.trim()}
|
|
121
|
+
onClick=${create}>${t('admin.subdomains.create')}</button>
|
|
122
|
+
${formError && html`<div class="adm-text-error adm-text-sm">${formError}</div>`}
|
|
123
|
+
</div>
|
|
124
|
+
`}
|
|
125
|
+
|
|
126
|
+
${error && html`<${ErrorBox} message=${error} />`}
|
|
127
|
+
${loading ? html`<${Spinner} />` : sites.length === 0
|
|
128
|
+
? html`<${Empty} text=${t('admin.subdomains.empty')} />`
|
|
129
|
+
: html`
|
|
130
|
+
<div class="scrollable">
|
|
131
|
+
<table>
|
|
132
|
+
<thead><tr>
|
|
133
|
+
<th>${t('admin.subdomains.subdomain')}</th>
|
|
134
|
+
<th>${t('admin.subdomains.kind')}</th>
|
|
135
|
+
<th>${t('admin.subdomains.target')}</th>
|
|
136
|
+
<th>${t('admin.subdomains.enabled')}</th>
|
|
137
|
+
<th>${t('admin.subdomains.createdBy')}</th>
|
|
138
|
+
<th>${t('admin.subdomains.created')}</th>
|
|
139
|
+
<th></th>
|
|
140
|
+
</tr></thead>
|
|
141
|
+
<tbody>
|
|
142
|
+
${sites.map(s => html`
|
|
143
|
+
<tr>
|
|
144
|
+
<td class="mono">
|
|
145
|
+
<a href=${siteUrl(s.subdomain)} target="_blank" rel="noopener">\u{1F310} ${s.subdomain}</a>
|
|
146
|
+
</td>
|
|
147
|
+
<td>${s.kind === 'app' ? t('admin.subdomains.kindApp') : t('admin.subdomains.kindRedirect')}</td>
|
|
148
|
+
<td class="mono">${s.target}</td>
|
|
149
|
+
<td>
|
|
150
|
+
<input type="checkbox" checked=${s.enabled} onChange=${() => toggleEnabled(s)}
|
|
151
|
+
title=${t('admin.subdomains.enabled')} />
|
|
152
|
+
</td>
|
|
153
|
+
<td class="mono">${s.createdBy}</td>
|
|
154
|
+
<td>${dt(s.createdAt)}</td>
|
|
155
|
+
<td>
|
|
156
|
+
<button class="adm-btn-sm adm-btn-danger"
|
|
157
|
+
onClick=${() => setDeleting({ subdomain: s.subdomain, typed: '' })}>
|
|
158
|
+
${t('admin.subdomains.delete')}
|
|
159
|
+
</button>
|
|
160
|
+
</td>
|
|
161
|
+
</tr>
|
|
162
|
+
`)}
|
|
163
|
+
</tbody>
|
|
164
|
+
</table>
|
|
165
|
+
</div>
|
|
166
|
+
`}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<${Modal} open=${!!deleting} onClose=${() => setDeleting(null)} title=${t('admin.subdomains.deleteTitle')}>
|
|
170
|
+
${deleting && html`
|
|
171
|
+
<p>${t('admin.subdomains.deleteConfirm')} <strong class="mono">${deleting.subdomain}</strong></p>
|
|
172
|
+
<input class="adm-input adm-input-full" value=${deleting.typed}
|
|
173
|
+
onInput=${e => setDeleting({ ...deleting, typed: e.target.value })} />
|
|
174
|
+
<div class="adm-flex adm-mt-md adm-subdomain-modal-actions">
|
|
175
|
+
<button class="btn-ghost" onClick=${() => setDeleting(null)}>${t('common.cancel')}</button>
|
|
176
|
+
<button class="btn-danger-solid" disabled=${deleting.typed !== deleting.subdomain}
|
|
177
|
+
onClick=${doDelete}>${t('admin.subdomains.delete')}</button>
|
|
178
|
+
</div>
|
|
179
|
+
`}
|
|
180
|
+
<//>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
@@ -52,6 +52,7 @@ import CapabilitiesAdminTab from './admin/capabilities-tab.js';
|
|
|
52
52
|
import AgentTasksAdminTab from './admin/agent-tasks-tab.js';
|
|
53
53
|
import SharingGroupsAdminTab from './admin/sharing-groups-tab.js';
|
|
54
54
|
import AgentIntegrationAdminTab from './admin/agent-integration-tab.js';
|
|
55
|
+
import SubdomainsAdminTab from './admin/subdomains-tab.js';
|
|
55
56
|
|
|
56
57
|
// ── Sidebar nav structure ──
|
|
57
58
|
const NAV_GROUPS = [
|
|
@@ -64,6 +65,7 @@ const NAV_GROUPS = [
|
|
|
64
65
|
{ id: 'maintenance', icon: '\u{1F6A7}', key: 'dashboard.maintenance',component: MaintenanceTab },
|
|
65
66
|
{ id: 'hooks', icon: '\u{1F517}', key: 'dashboard.hooks', component: HooksTab },
|
|
66
67
|
{ id: 'portal', icon: '\u{1F310}', key: 'dashboard.portal', component: PortalTab },
|
|
68
|
+
{ id: 'subdomains', icon: '\u{1F517}', key: 'admin.subdomains.title', component: SubdomainsAdminTab },
|
|
67
69
|
{ id: 'stats', icon: '\u{1F4C8}', key: 'dashboard.stats', component: StatsTab },
|
|
68
70
|
{ id: 'prompts', icon: '\u{1F4DD}', key: 'dashboard.promptsTab', component: PromptsTab },
|
|
69
71
|
]},
|