aimeat 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -3
- package/dist/.env.example +5 -1
- package/dist/locales/en.json +231 -1
- package/dist/locales/fi.json +233 -1
- package/dist/public/components/Markdown.js +88 -15
- package/dist/public/components/Mermaid.js +59 -0
- package/dist/public/components/NotificationBell.js +94 -0
- package/dist/public/css/theme.css +26 -0
- package/dist/public/css/views/admin.css +16 -0
- package/dist/public/css/views/agents-detail.css +93 -0
- package/dist/public/css/views/profile.css +241 -2
- package/dist/public/js/api.js +11 -5
- package/dist/public/js/services/admin.js +4 -0
- package/dist/public/js/services/agents.js +19 -0
- package/dist/public/js/services/organisms.js +822 -2
- package/dist/public/lib/mermaid/README.md +6 -0
- package/dist/public/lib/mermaid/mermaid.min.js +3405 -0
- package/dist/public/lib/toastui/toastui-editor-all.min.js +24 -0
- package/dist/public/lib/toastui/toastui-editor.min.css +6 -0
- package/dist/public/llms-template.txt +4 -0
- package/dist/public/spa.html +8 -0
- package/dist/public/views/admin/security-tab.js +93 -0
- package/dist/public/views/admin.js +2 -0
- package/dist/public/views/doc-solo.js +102 -0
- package/dist/public/views/profile/agents/agent-card.js +67 -8
- package/dist/public/views/profile/agents/tab-messages.js +51 -7
- package/dist/public/views/profile/agents-tab.js +184 -4
- package/dist/public/views/profile/agents-tasks-subtab.js +11 -1
- package/dist/public/views/profile/generator-dashboard/use-autopilot.js +5 -2
- package/dist/public/views/profile/landing-page.js +12 -4
- package/dist/public/views/profile/organisms-tab.js +1640 -2
- package/dist/public/views/profile/schedule-item.js +15 -1
- package/dist/public/views/profile.js +6 -3
- package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/index.js +2 -0
- package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +31 -0
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/workspaces.d.ts +15 -0
- package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -0
- package/dist/src/cli/connect/mcp/tools/workspaces.js +240 -0
- package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -0
- package/dist/src/cli/connect/tool-call.d.ts +5 -0
- package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call.js +255 -0
- package/dist/src/cli/connect/tool-call.js.map +1 -1
- package/dist/src/cli/init-wizard.d.ts.map +1 -1
- package/dist/src/cli/init-wizard.js +16 -8
- package/dist/src/cli/init-wizard.js.map +1 -1
- package/dist/src/config.d.ts +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +4 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +998 -23
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/client.d.ts +1 -0
- package/dist/src/generated/prisma-postgres/client.js +5 -0
- package/dist/src/generated/prisma-postgres/default.d.ts +1 -0
- package/dist/src/generated/prisma-postgres/default.js +5 -0
- package/dist/src/generated/prisma-postgres/edge.d.ts +1 -0
- package/dist/src/generated/prisma-postgres/edge.js +1530 -0
- package/dist/src/generated/prisma-postgres/index-browser.js +1516 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +128983 -0
- package/dist/src/generated/prisma-postgres/index.js +1551 -0
- package/dist/src/generated/prisma-postgres/package.json +183 -0
- package/dist/src/generated/prisma-postgres/query_engine-windows.dll.node +0 -0
- package/dist/src/generated/prisma-postgres/query_engine_bg.js +2 -0
- package/dist/src/generated/prisma-postgres/query_engine_bg.wasm +0 -0
- package/dist/src/generated/prisma-postgres/runtime/edge-esm.js +35 -0
- package/dist/src/generated/prisma-postgres/runtime/edge.js +35 -0
- package/dist/src/generated/prisma-postgres/runtime/index-browser.d.ts +370 -0
- package/dist/src/generated/prisma-postgres/runtime/index-browser.js +17 -0
- package/dist/src/generated/prisma-postgres/runtime/library.d.ts +3982 -0
- package/dist/src/generated/prisma-postgres/runtime/library.js +147 -0
- package/dist/src/generated/prisma-postgres/runtime/react-native.js +84 -0
- package/dist/src/generated/prisma-postgres/runtime/wasm-compiler-edge.js +85 -0
- package/dist/src/generated/prisma-postgres/runtime/wasm-engine-edge.js +38 -0
- package/dist/src/generated/prisma-postgres/schema.prisma +1465 -0
- package/dist/src/generated/prisma-postgres/wasm-edge-light-loader.mjs +5 -0
- package/dist/src/generated/prisma-postgres/wasm-worker-loader.mjs +5 -0
- package/dist/src/generated/prisma-postgres/wasm.d.ts +1 -0
- package/dist/src/generated/prisma-postgres/wasm.js +1537 -0
- package/dist/src/index.js +1 -1
- package/dist/src/mcp/agent-messages.d.ts +3 -0
- package/dist/src/mcp/agent-messages.d.ts.map +1 -1
- package/dist/src/mcp/agent-messages.js +9 -3
- package/dist/src/mcp/agent-messages.js.map +1 -1
- package/dist/src/mcp/annotations.d.ts.map +1 -1
- package/dist/src/mcp/annotations.js +12 -0
- package/dist/src/mcp/annotations.js.map +1 -1
- package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
- package/dist/src/mcp/catalog/definitions.js +139 -0
- package/dist/src/mcp/catalog/definitions.js.map +1 -1
- package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
- package/dist/src/mcp/catalog/surfaces.js +9 -2
- package/dist/src/mcp/catalog/surfaces.js.map +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js +2 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/organisms.d.ts +2 -0
- package/dist/src/mcp/organisms.d.ts.map +1 -1
- package/dist/src/mcp/organisms.js +106 -21
- package/dist/src/mcp/organisms.js.map +1 -1
- package/dist/src/mcp/workspaces.d.ts +34 -0
- package/dist/src/mcp/workspaces.d.ts.map +1 -0
- package/dist/src/mcp/workspaces.js +464 -0
- package/dist/src/mcp/workspaces.js.map +1 -0
- package/dist/src/middleware/workspace-access.d.ts +13 -0
- package/dist/src/middleware/workspace-access.d.ts.map +1 -1
- package/dist/src/middleware/workspace-access.js +12 -7
- package/dist/src/middleware/workspace-access.js.map +1 -1
- package/dist/src/models/schemas.d.ts +2 -2
- package/dist/src/routes/admin-prompts.d.ts.map +1 -1
- package/dist/src/routes/admin-prompts.js +4 -1
- package/dist/src/routes/admin-prompts.js.map +1 -1
- package/dist/src/routes/admin-security.d.ts +19 -0
- package/dist/src/routes/admin-security.d.ts.map +1 -0
- package/dist/src/routes/admin-security.js +87 -0
- package/dist/src/routes/admin-security.js.map +1 -0
- package/dist/src/routes/agent-messages.d.ts +3 -0
- package/dist/src/routes/agent-messages.d.ts.map +1 -1
- package/dist/src/routes/agent-messages.js +27 -2
- package/dist/src/routes/agent-messages.js.map +1 -1
- package/dist/src/routes/agent-tasks.d.ts +5 -1
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +43 -3
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/apps.d.ts +13 -0
- package/dist/src/routes/apps.d.ts.map +1 -1
- package/dist/src/routes/apps.js +74 -42
- package/dist/src/routes/apps.js.map +1 -1
- package/dist/src/routes/bootstrap.d.ts.map +1 -1
- package/dist/src/routes/bootstrap.js +1 -0
- package/dist/src/routes/bootstrap.js.map +1 -1
- package/dist/src/routes/extensions.d.ts.map +1 -1
- package/dist/src/routes/extensions.js +5 -1
- package/dist/src/routes/extensions.js.map +1 -1
- package/dist/src/routes/generator-autopilot.d.ts +1 -0
- package/dist/src/routes/generator-autopilot.d.ts.map +1 -1
- package/dist/src/routes/generator-autopilot.js +8 -2
- package/dist/src/routes/generator-autopilot.js.map +1 -1
- package/dist/src/routes/generator.d.ts.map +1 -1
- package/dist/src/routes/generator.js +20 -0
- package/dist/src/routes/generator.js.map +1 -1
- package/dist/src/routes/libs.d.ts +4 -0
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +134 -5
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/memory.d.ts +2 -0
- package/dist/src/routes/memory.d.ts.map +1 -1
- package/dist/src/routes/memory.js +20 -10
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/notifications.d.ts +18 -0
- package/dist/src/routes/notifications.d.ts.map +1 -0
- package/dist/src/routes/notifications.js +64 -0
- package/dist/src/routes/notifications.js.map +1 -0
- package/dist/src/routes/organisms.d.ts +29 -0
- package/dist/src/routes/organisms.d.ts.map +1 -1
- package/dist/src/routes/organisms.js +1042 -2
- package/dist/src/routes/organisms.js.map +1 -1
- package/dist/src/routes/schedules.d.ts.map +1 -1
- package/dist/src/routes/schedules.js +10 -2
- package/dist/src/routes/schedules.js.map +1 -1
- package/dist/src/routes/storage-files.d.ts +5 -0
- package/dist/src/routes/storage-files.d.ts.map +1 -1
- package/dist/src/routes/storage-files.js +83 -16
- package/dist/src/routes/storage-files.js.map +1 -1
- package/dist/src/routes/upload.d.ts +3 -0
- package/dist/src/routes/upload.d.ts.map +1 -1
- package/dist/src/routes/upload.js +9 -1
- package/dist/src/routes/upload.js.map +1 -1
- package/dist/src/server-bootstrap/config-init.d.ts.map +1 -1
- package/dist/src/server-bootstrap/config-init.js +1 -0
- package/dist/src/server-bootstrap/config-init.js.map +1 -1
- package/dist/src/server-bootstrap/middleware-guards.d.ts.map +1 -1
- package/dist/src/server-bootstrap/middleware-guards.js +4 -2
- package/dist/src/server-bootstrap/middleware-guards.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +4 -0
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server-bootstrap/service-init.d.ts +17 -0
- package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
- package/dist/src/server-bootstrap/service-init.js +26 -0
- package/dist/src/server-bootstrap/service-init.js.map +1 -1
- package/dist/src/services/access-guard.d.ts +52 -0
- package/dist/src/services/access-guard.d.ts.map +1 -0
- package/dist/src/services/access-guard.js +30 -0
- package/dist/src/services/access-guard.js.map +1 -0
- package/dist/src/services/config-schema.js +1 -1
- package/dist/src/services/config-schema.js.map +1 -1
- package/dist/src/services/consent.d.ts.map +1 -1
- package/dist/src/services/consent.js +37 -1
- package/dist/src/services/consent.js.map +1 -1
- package/dist/src/services/gate-expiry.d.ts +25 -0
- package/dist/src/services/gate-expiry.d.ts.map +1 -0
- package/dist/src/services/gate-expiry.js +26 -0
- package/dist/src/services/gate-expiry.js.map +1 -0
- package/dist/src/services/gate-policy.d.ts +50 -0
- package/dist/src/services/gate-policy.d.ts.map +1 -0
- package/dist/src/services/gate-policy.js +82 -0
- package/dist/src/services/gate-policy.js.map +1 -0
- package/dist/src/services/generator-autopilot.d.ts +18 -1
- package/dist/src/services/generator-autopilot.d.ts.map +1 -1
- package/dist/src/services/generator-autopilot.js +94 -154
- package/dist/src/services/generator-autopilot.js.map +1 -1
- package/dist/src/services/handbooks/agent.d.ts +3 -1
- package/dist/src/services/handbooks/agent.d.ts.map +1 -1
- package/dist/src/services/handbooks/agent.js +10 -3
- package/dist/src/services/handbooks/agent.js.map +1 -1
- package/dist/src/services/manifest-schema.d.ts +48 -0
- package/dist/src/services/manifest-schema.d.ts.map +1 -0
- package/dist/src/services/manifest-schema.js +88 -0
- package/dist/src/services/manifest-schema.js.map +1 -0
- package/dist/src/services/notify.d.ts +19 -0
- package/dist/src/services/notify.d.ts.map +1 -0
- package/dist/src/services/notify.js +43 -0
- package/dist/src/services/notify.js.map +1 -0
- package/dist/src/services/organism-export.d.ts +13 -0
- package/dist/src/services/organism-export.d.ts.map +1 -0
- package/dist/src/services/organism-export.js +58 -0
- package/dist/src/services/organism-export.js.map +1 -0
- package/dist/src/services/organism-import.d.ts +16 -0
- package/dist/src/services/organism-import.d.ts.map +1 -0
- package/dist/src/services/organism-import.js +82 -0
- package/dist/src/services/organism-import.js.map +1 -0
- package/dist/src/services/prompt-defaults.d.ts.map +1 -1
- package/dist/src/services/prompt-defaults.js +75 -0
- package/dist/src/services/prompt-defaults.js.map +1 -1
- package/dist/src/services/safe-zip.d.ts +27 -0
- package/dist/src/services/safe-zip.d.ts.map +1 -0
- package/dist/src/services/safe-zip.js +131 -0
- package/dist/src/services/safe-zip.js.map +1 -0
- package/dist/src/services/scheduler.d.ts +19 -1
- package/dist/src/services/scheduler.d.ts.map +1 -1
- package/dist/src/services/scheduler.js +49 -17
- package/dist/src/services/scheduler.js.map +1 -1
- package/dist/src/services/security-incident.d.ts +25 -0
- package/dist/src/services/security-incident.d.ts.map +1 -0
- package/dist/src/services/security-incident.js +49 -0
- package/dist/src/services/security-incident.js.map +1 -0
- package/dist/src/services/skill-bundle/generator.d.ts +2 -0
- package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
- package/dist/src/services/skill-bundle/generator.js +11 -5
- package/dist/src/services/skill-bundle/generator.js.map +1 -1
- package/dist/src/services/template-bundles.d.ts +37 -0
- package/dist/src/services/template-bundles.d.ts.map +1 -0
- package/dist/src/services/template-bundles.js +130 -0
- package/dist/src/services/template-bundles.js.map +1 -0
- package/dist/src/services/workspace-export.d.ts +63 -0
- package/dist/src/services/workspace-export.d.ts.map +1 -0
- package/dist/src/services/workspace-export.js +145 -0
- package/dist/src/services/workspace-export.js.map +1 -0
- package/dist/src/services/workspace-import.d.ts +44 -0
- package/dist/src/services/workspace-import.d.ts.map +1 -0
- package/dist/src/services/workspace-import.js +120 -0
- package/dist/src/services/workspace-import.js.map +1 -0
- package/dist/src/services/workspace-meta.d.ts +39 -0
- package/dist/src/services/workspace-meta.d.ts.map +1 -0
- package/dist/src/services/workspace-meta.js +95 -0
- package/dist/src/services/workspace-meta.js.map +1 -0
- package/dist/src/storage/interface.d.ts +26 -0
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/pattern-utils.d.ts +26 -1
- package/dist/src/storage/pattern-utils.d.ts.map +1 -1
- package/dist/src/storage/pattern-utils.js +37 -8
- package/dist/src/storage/pattern-utils.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +50 -7
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +244 -15
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/postgres/index.d.ts +6 -0
- package/dist/src/storage/providers/postgres/index.d.ts.map +1 -0
- package/dist/src/storage/providers/postgres/index.js +33 -0
- package/dist/src/storage/providers/postgres/index.js.map +1 -0
- package/dist/src/storage/providers/sqlite/index.d.ts +11 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +152 -2
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts +1 -0
- package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.js +5 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/storage-file.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/storage-file.js +6 -2
- package/dist/src/storage/providers/sqlite/repos/storage-file.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +23 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/app.repository.d.ts +42 -0
- package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/organism.repository.d.ts +8 -1
- package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
- package/dist/src/storage/storage-factory.d.ts +1 -1
- package/dist/src/storage/storage-factory.d.ts.map +1 -1
- package/dist/src/storage/storage-factory.js +6 -0
- package/dist/src/storage/storage-factory.js.map +1 -1
- package/dist/src/utils/env-config.js +5 -5
- package/dist/src/utils/env-config.js.map +1 -1
- package/dist/src/utils/env-validator.js +6 -6
- package/dist/src/utils/env-validator.js.map +1 -1
- package/dist/src/utils/stable-json.d.ts +22 -0
- package/dist/src/utils/stable-json.d.ts.map +1 -0
- package/dist/src/utils/stable-json.js +36 -0
- package/dist/src/utils/stable-json.js.map +1 -0
- package/dist/static/app-catalog.html +936 -221
- package/package.json +6 -3
- package/prisma/schema.postgres.prisma +1467 -0
- package/prisma/schema.prisma +27 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AIMEAT Organisms Service
|
|
3
|
-
* Organism CRUD, membership, join requests
|
|
3
|
+
* Organism CRUD, membership, join requests, and the manifest-driven workspace
|
|
4
|
+
* (a "project" is just an organism with a meta.manifest): apply a template,
|
|
5
|
+
* read the workspace, write/publish drafts, and resolve gate approvals.
|
|
4
6
|
*/
|
|
5
|
-
import { api, apiGet, apiPost, apiDelete } from '/js/api.js';
|
|
7
|
+
import { api, apiGet, apiPost, apiPut, apiPatch, apiDelete } from '/js/api.js';
|
|
6
8
|
|
|
7
9
|
/** List organisms. */
|
|
8
10
|
export async function listOrganisms(opts = {}) {
|
|
@@ -72,3 +74,821 @@ export async function addAdmin(id, targetGhii) {
|
|
|
72
74
|
export async function removeAdmin(id, ghii) {
|
|
73
75
|
return apiDelete(`/v1/organisms/${encodeURIComponent(id)}/admins/${encodeURIComponent(ghii)}`);
|
|
74
76
|
}
|
|
77
|
+
|
|
78
|
+
// ── Workspace (manifest-driven; a "project" = an organism with a manifest) ──
|
|
79
|
+
|
|
80
|
+
/** Object types in the shipped "project" template (mirror of docs/csm-bundles/project/). */
|
|
81
|
+
export const PROJECT_OBJECT_TYPES = [
|
|
82
|
+
{ name: 'goal', namespace: 'meta.goals', writeRole: 'owner', versioned: true },
|
|
83
|
+
{ name: 'plan', namespace: 'meta.plans', writeRole: 'owner', versioned: true },
|
|
84
|
+
{ name: 'deliverable', namespace: 'shared.deliverables', writeRole: 'member', versioned: true },
|
|
85
|
+
{ name: 'decision', namespace: 'meta.decisions', writeRole: 'member', versioned: false, append: true },
|
|
86
|
+
{ name: 'resource', namespace: 'shared.resources', writeRole: 'member', versioned: true },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/** Compiled JSON Schemas per object type (mirror of the bundle CSMs). */
|
|
90
|
+
export const PROJECT_SCHEMAS = {
|
|
91
|
+
'meta.goals': { type: 'object', required: ['id', 'title', 'status'], properties: {
|
|
92
|
+
id: { type: 'string', minLength: 1 }, title: { type: 'string', minLength: 1 },
|
|
93
|
+
status: { type: 'string', enum: ['open', 'met', 'dropped'] },
|
|
94
|
+
definitionOfDone: { type: 'array', items: { type: 'string' } }, gateId: { type: 'string' } } },
|
|
95
|
+
'meta.plans': { type: 'object', required: ['id', 'approach', 'version', 'status'], properties: {
|
|
96
|
+
id: { type: 'string', minLength: 1 }, approach: { type: 'string', minLength: 1 },
|
|
97
|
+
version: { type: 'integer', minimum: 1 }, status: { type: 'string', enum: ['proposed', 'approved', 'superseded'] },
|
|
98
|
+
steps: { type: 'array', items: { type: 'string' } }, gateId: { type: 'string' } } },
|
|
99
|
+
'shared.deliverables': { type: 'object', required: ['id', 'title', 'status'], properties: {
|
|
100
|
+
id: { type: 'string', minLength: 1 }, title: { type: 'string', minLength: 1 },
|
|
101
|
+
status: { type: 'string', enum: ['proposed', 'in_progress', 'delivered', 'accepted', 'rejected'] },
|
|
102
|
+
description: { type: 'string' }, acceptanceCriteria: { type: 'array', items: { type: 'string' } } } },
|
|
103
|
+
'meta.decisions': { type: 'object', required: ['ts', 'kind', 'by', 'summary'], properties: {
|
|
104
|
+
ts: { type: 'string', minLength: 1 }, kind: { type: 'string', enum: ['decision', 'plan-change', 'deliverable', 'rating'] },
|
|
105
|
+
by: { type: 'string', minLength: 1 }, summary: { type: 'string', minLength: 1 } } },
|
|
106
|
+
'shared.resources': { type: 'object', required: ['id', 'kind', 'label', 'origin', 'pointer', 'visibility'], properties: {
|
|
107
|
+
id: { type: 'string', minLength: 1 }, kind: { type: 'string', enum: ['doc', 'code', 'asset', 'knowledge', 'link'] },
|
|
108
|
+
label: { type: 'string', minLength: 1 }, origin: { type: 'string', enum: ['local', 'referenced', 'link'] },
|
|
109
|
+
pointer: { type: 'string', minLength: 1 }, visibility: { type: 'string', enum: ['private', 'owner', 'group', 'public'] } } },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function projectManifest(orgId, name, summary) {
|
|
113
|
+
return {
|
|
114
|
+
manifestVersion: '1.0', id: orgId, name, kind: 'project', language: 'en',
|
|
115
|
+
summary: summary || '', status: 'active',
|
|
116
|
+
entry: { readme: `organism.${orgId}.meta.readme`, loadHint: 'readme -> goals -> plans -> deliverables -> decisions' },
|
|
117
|
+
objectTypes: PROJECT_OBJECT_TYPES.map(ot => ({
|
|
118
|
+
name: ot.name, schemaRef: `schema:project/${ot.name}@1`, namespace: ot.namespace,
|
|
119
|
+
cardinality: 'many', backing: 'memory', writeRole: ot.writeRole,
|
|
120
|
+
...(ot.append ? { append: true } : {}), versioned: ot.versioned,
|
|
121
|
+
})),
|
|
122
|
+
policy: { agentAutonomy: 'L3', alwaysGate: ['external-release', 'spend', 'data-egress', 'data-model-change'] },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Fetch the managed "manifest architect" prompt (the generator's instruction set). */
|
|
127
|
+
export async function getManifestArchitectPrompt() {
|
|
128
|
+
try {
|
|
129
|
+
const resp = await apiGet('/v1/portal/prompts/manifest-architect');
|
|
130
|
+
return resp?.data?.prompt || '';
|
|
131
|
+
} catch { return ''; }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Self-contained, positively-framed one-shot prompt for the workspace generator (and the
|
|
135
|
+
* copy-into-any-AI-chat path). Kept separate from the interactive `manifest-architect` prompt,
|
|
136
|
+
* whose interview + CSM-YAML output would contradict the single-JSON-object contract here. */
|
|
137
|
+
const GENERATOR_PROMPT = `You are the AIMEAT Workspace Architect. Design a workspace from the user's request and respond with exactly one JSON object — your reply begins with { and ends with }.
|
|
138
|
+
|
|
139
|
+
A workspace is a set of object types. Each type is one of two modes:
|
|
140
|
+
- "records" — structured items with consistent fields, validated by a schema. Use this for trackable things: milestones, tasks, risks, contacts, decisions.
|
|
141
|
+
- "document" — free-form markdown that grows organically, with no schema. Use this whenever the user asks for documents, a wiki, notes, guides, design docs, or an open / free-form format.
|
|
142
|
+
|
|
143
|
+
Respond with this shape:
|
|
144
|
+
{
|
|
145
|
+
"manifest": {
|
|
146
|
+
"manifestVersion": "1.0",
|
|
147
|
+
"id": "",
|
|
148
|
+
"name": "<short workspace name>",
|
|
149
|
+
"kind": "<short-kebab-case kind, e.g. game-dev>",
|
|
150
|
+
"status": "active",
|
|
151
|
+
"objectTypes": [ <one entry per type> ],
|
|
152
|
+
"policy": { "agentAutonomy": "L3", "alwaysGate": [] }
|
|
153
|
+
},
|
|
154
|
+
"schemas": { <one JSON Schema per RECORDS type, keyed by that type's namespace> },
|
|
155
|
+
"examples": { <1-3 sample records per RECORDS type, keyed by that type's namespace> }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Give every objectType all of these fields:
|
|
159
|
+
- "name": a short singular noun, e.g. "milestone" or "design-doc".
|
|
160
|
+
- "schemaRef": a label string you choose, e.g. "schema:milestone@1". Include it for every type, documents included.
|
|
161
|
+
- "namespace": exactly "shared.<plural>" for collaborative data, or "meta.<plural>" for owner-controlled data — e.g. "shared.milestones", "meta.decisions". The namespace is just that prefix plus the plural name; keep it to those two forms.
|
|
162
|
+
- "backing": "memory".
|
|
163
|
+
- "writeRole": "member" (any member writes), "admin", or "owner".
|
|
164
|
+
- "cardinality": "many".
|
|
165
|
+
- "versioned": true.
|
|
166
|
+
- "mode": "records" or "document".
|
|
167
|
+
|
|
168
|
+
For each RECORDS type, add a JSON Schema under "schemas" keyed by its namespace:
|
|
169
|
+
{ "type": "object", "required": ["id", ...], "properties": { "id": { "type": "string" }, ... } }
|
|
170
|
+
Give every record an "id" string property. Use "enum" for status-like fields and "format": "date" (or "date-time") for date fields.
|
|
171
|
+
|
|
172
|
+
For each DOCUMENT type, the content is free markdown — keep it out of "schemas" and "examples" (a document needs neither).
|
|
173
|
+
|
|
174
|
+
Under "examples", add 1-3 realistic sample records per RECORDS type, with ids like "example-1", each valid against that type's schema.
|
|
175
|
+
|
|
176
|
+
Worked example — request "track game milestones plus free-form design docs":
|
|
177
|
+
{
|
|
178
|
+
"manifest": {
|
|
179
|
+
"manifestVersion": "1.0", "id": "", "name": "Game Project", "kind": "game-dev", "status": "active",
|
|
180
|
+
"objectTypes": [
|
|
181
|
+
{ "name": "milestone", "schemaRef": "schema:milestone@1", "namespace": "shared.milestones", "backing": "memory", "writeRole": "member", "cardinality": "many", "versioned": true, "mode": "records" },
|
|
182
|
+
{ "name": "design-doc", "schemaRef": "schema:design-doc@1", "namespace": "shared.design-docs", "backing": "memory", "writeRole": "member", "cardinality": "many", "versioned": true, "mode": "document" }
|
|
183
|
+
],
|
|
184
|
+
"policy": { "agentAutonomy": "L3", "alwaysGate": [] }
|
|
185
|
+
},
|
|
186
|
+
"schemas": {
|
|
187
|
+
"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" } } }
|
|
188
|
+
},
|
|
189
|
+
"examples": {
|
|
190
|
+
"shared.milestones": [ { "id": "example-1", "title": "Vertical slice", "status": "planned", "due_date": "2026-09-01" } ]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Match the user's domain and language. Respond with the JSON object only.`;
|
|
195
|
+
|
|
196
|
+
/** The system instructions for the one-shot generator + the copy-into-any-AI-chat path. */
|
|
197
|
+
export async function generatorSystemPrompt() {
|
|
198
|
+
return GENERATOR_PROMPT;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Frame the request — when restructuring an existing workspace, give the AI the current manifest
|
|
202
|
+
* so it EXTENDS rather than starts over (additive: keep existing types unless told to remove). */
|
|
203
|
+
function frameRequest(description, currentManifest) {
|
|
204
|
+
const req = description || '(describe the workspace you want)';
|
|
205
|
+
if (!currentManifest) return req;
|
|
206
|
+
return `Current manifest — EXTEND it: keep its existing objectTypes and their namespaces unless I explicitly ask to remove them, and add what I describe.\n${JSON.stringify(currentManifest)}\n\nWhat to change or add: ${req}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The full text to copy into any AI chat (instructions + the user's request). */
|
|
210
|
+
export async function buildGeneratorPrompt(description, currentManifest) {
|
|
211
|
+
return (await generatorSystemPrompt()) + `\n\nUser request: ${frameRequest(description, currentManifest)}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Parse an AI response into { manifest, schemas } — strips markdown fences + surrounding prose. */
|
|
215
|
+
export function parseGenerated(text) {
|
|
216
|
+
const stripped = String(text || '').replace(/^```(?:json)?\s*|\s*```$/g, '').trim();
|
|
217
|
+
let json;
|
|
218
|
+
try { json = JSON.parse(stripped); }
|
|
219
|
+
catch {
|
|
220
|
+
const m = stripped.match(/\{[\s\S]*\}/);
|
|
221
|
+
if (!m) throw new Error('No JSON object found in the response');
|
|
222
|
+
json = JSON.parse(m[0]);
|
|
223
|
+
}
|
|
224
|
+
if (!json.manifest || !json.schemas) throw new Error('Response is missing "manifest" or "schemas"');
|
|
225
|
+
return json;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Call the AI and return the RAW content string (the caller shows + validates it).
|
|
229
|
+
* Uses a long timeout (slow free models can take minutes) and no retry on timeout. */
|
|
230
|
+
export async function generateRaw(description, currentManifest) {
|
|
231
|
+
const resp = await api('/v1/ai/complete', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
prompt: frameRequest(description, currentManifest),
|
|
235
|
+
systemPrompt: await generatorSystemPrompt(),
|
|
236
|
+
modelRole: 'execution',
|
|
237
|
+
max_tokens: 3000,
|
|
238
|
+
app_id: 'organism-workspace',
|
|
239
|
+
}),
|
|
240
|
+
timeoutMs: 600_000,
|
|
241
|
+
retries: 0,
|
|
242
|
+
});
|
|
243
|
+
if (resp?.ok === false) { const e = new Error(resp?.error?.message || 'AI call failed'); e.code = resp?.error?.code; throw e; }
|
|
244
|
+
return String(resp?.data?.content || '');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Delete a workspace entirely — removes all organism.{id}.* memory + its schema locks. The
|
|
248
|
+
* organism stays (returns to "no workspace yet"). Deliberate typed-confirmation lives in the UI. */
|
|
249
|
+
export async function deleteWorkspace(orgId, wsId) {
|
|
250
|
+
const r = await apiDelete(`/v1/organisms/${encodeURIComponent(orgId)}/workspace?ws=${encodeURIComponent(wsId)}`);
|
|
251
|
+
// Drop the registry entry so the deleted workspace no longer lists.
|
|
252
|
+
const list = await listWorkspaces(orgId);
|
|
253
|
+
await saveWorkspaceRegistry(orgId, list.filter(w => w.id !== wsId)).catch(() => {});
|
|
254
|
+
return r;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Client-side validation of a parsed { manifest, schemas }. Returns an array of human-readable
|
|
258
|
+
* errors (empty = valid). Mirrors the backend manifest-format rules so we validate BEFORE saving. */
|
|
259
|
+
export function validateGenerated(generated) {
|
|
260
|
+
const errors = [];
|
|
261
|
+
const m = (generated && generated.manifest) || {};
|
|
262
|
+
const schemas = (generated && generated.schemas) || {};
|
|
263
|
+
if (!m.manifestVersion) errors.push('manifest.manifestVersion is required');
|
|
264
|
+
if (!m.name) errors.push('manifest.name is required');
|
|
265
|
+
if (!m.kind) errors.push('manifest.kind is required');
|
|
266
|
+
if (!['active', 'paused', 'done', 'archived'].includes(m.status)) errors.push('manifest.status must be one of: active, paused, done, archived');
|
|
267
|
+
const ots = Array.isArray(m.objectTypes) ? m.objectTypes : [];
|
|
268
|
+
if (ots.length === 0) errors.push('manifest.objectTypes must have at least one entry');
|
|
269
|
+
const backings = ['memory', 'tasks', 'storage', 'knowledge'];
|
|
270
|
+
const roles = ['owner', 'admin', 'member'];
|
|
271
|
+
for (const ot of ots) {
|
|
272
|
+
const n = (ot && ot.name) || '(unnamed)';
|
|
273
|
+
if (!ot || !ot.name) errors.push('an objectType is missing "name"');
|
|
274
|
+
if (!ot || !ot.schemaRef) errors.push(`objectType "${n}" is missing "schemaRef" (any label string, e.g. "schema:${n}@1")`);
|
|
275
|
+
if (!ot || !ot.namespace) errors.push(`objectType "${n}" is missing "namespace"`);
|
|
276
|
+
else if (!/^(meta|shared|member)\./.test(ot.namespace)) errors.push(`objectType "${n}" namespace must start with "meta." or "shared." (got "${ot.namespace}")`);
|
|
277
|
+
if (!ot || !backings.includes(ot.backing)) errors.push(`objectType "${n}" backing must be one of: ${backings.join(', ')}`);
|
|
278
|
+
if (!ot || !roles.includes(ot.writeRole)) errors.push(`objectType "${n}" writeRole must be one of: ${roles.join(', ')}`);
|
|
279
|
+
if (ot && ot.cardinality && !['one', 'many'].includes(ot.cardinality)) errors.push(`objectType "${n}" cardinality must be "one" or "many"`);
|
|
280
|
+
if (ot && ot.backing === 'memory' && ot.mode !== 'document' && !schemas[ot.namespace]) errors.push(`no schema provided for memory objectType "${n}" (namespace "${ot.namespace}")`);
|
|
281
|
+
}
|
|
282
|
+
for (const [ns, sc] of Object.entries(schemas)) {
|
|
283
|
+
if (!sc || typeof sc !== 'object' || sc.type !== 'object') errors.push(`schema "${ns}" must be a JSON Schema object with type:"object"`);
|
|
284
|
+
}
|
|
285
|
+
return errors;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** A prompt the user can paste back to their AI chat to fix the listed validation problems. */
|
|
289
|
+
export function buildFixPrompt(jsonText, errors) {
|
|
290
|
+
return `The JSON you produced for an AIMEAT workspace has validation problems. Fix ONLY these issues and output the corrected JSON object again (with "manifest" and "schemas"), no prose, no markdown fences.
|
|
291
|
+
|
|
292
|
+
Problems:
|
|
293
|
+
${(errors || []).map(e => '- ' + e).join('\n')}
|
|
294
|
+
|
|
295
|
+
The JSON you produced:
|
|
296
|
+
${jsonText}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Apply a generated (or any) workspace to an organism: register its schemas + write the manifest. */
|
|
300
|
+
/** Key root for one workspace — an organism holds many workspaces under organism.{id}.w.{wsId}. */
|
|
301
|
+
export function wsRoot(orgId, wsId) { return `organism.${orgId}.w.${wsId}`; }
|
|
302
|
+
|
|
303
|
+
export async function applyGeneratedWorkspace(orgId, wsId, generated) {
|
|
304
|
+
const root = wsRoot(orgId, wsId);
|
|
305
|
+
for (const [namespace, schema] of Object.entries(generated.schemas || {})) {
|
|
306
|
+
await apiPut(`/v1/memory/${encodeURIComponent(`${root}.${namespace}`)}/schema`, { schema, apply_to: 'prefix', schema_mode: 'strict' });
|
|
307
|
+
}
|
|
308
|
+
const manifest = { ...generated.manifest, id: orgId, status: generated.manifest.status || 'active' };
|
|
309
|
+
await apiPost('/v1/memory', { key: `${root}.meta.manifest`, value: manifest, visibility: 'private' });
|
|
310
|
+
await apiPost('/v1/memory', { key: `${root}.meta.readme`, value: `# ${manifest.name || 'Workspace'}\n\n${manifest.summary || ''}`, visibility: 'private' });
|
|
311
|
+
// Write any example instances as DRAFTS (clearly not-yet-published samples). Best-effort: a
|
|
312
|
+
// sample that doesn't validate is skipped, not fatal.
|
|
313
|
+
let n = 0;
|
|
314
|
+
for (const [namespace, items] of Object.entries(generated.examples || {})) {
|
|
315
|
+
if (!Array.isArray(items)) continue;
|
|
316
|
+
for (const item of items.slice(0, 5)) {
|
|
317
|
+
n++;
|
|
318
|
+
const id = String((item && item.id) || `example-${n}`).replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
319
|
+
await apiPost('/v1/memory', { key: `${root}.${namespace}.${id}.draft`, value: { ...item, id }, visibility: 'private' }).catch(() => {});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Apply the project template to a workspace (register schemas + write the manifest + readme). */
|
|
325
|
+
export async function applyProjectTemplate(orgId, wsId, name, summary) {
|
|
326
|
+
const root = wsRoot(orgId, wsId);
|
|
327
|
+
for (const [namespace, schema] of Object.entries(PROJECT_SCHEMAS)) {
|
|
328
|
+
await apiPut(`/v1/memory/${encodeURIComponent(`${root}.${namespace}`)}/schema`, { schema, apply_to: 'prefix', schema_mode: 'strict' });
|
|
329
|
+
}
|
|
330
|
+
await apiPost('/v1/memory', { key: `${root}.meta.manifest`, value: projectManifest(orgId, name, summary), visibility: 'private' });
|
|
331
|
+
await apiPost('/v1/memory', { key: `${root}.meta.readme`, value: `# ${name}\n\n${summary || ''}`, visibility: 'private' });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Participants of a workspace — node → owner → agents, derived from record traces + membership.
|
|
335
|
+
* Agent names are revealed only for the caller's own agents (others come back anonymized). */
|
|
336
|
+
export async function getWorkspaceParticipants(orgId, wsId) {
|
|
337
|
+
try {
|
|
338
|
+
const r = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/participants?ws=${encodeURIComponent(wsId)}`);
|
|
339
|
+
return r?.data || { nodes: [], viewerOwner: '' };
|
|
340
|
+
} catch { return { nodes: [], viewerOwner: '' }; }
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Build the participants chart: which node each identity comes from, who is human, and whose agent
|
|
344
|
+
* each agent is. The caller's own agents are named; everyone else's are ghost "agent" boxes. */
|
|
345
|
+
export function buildParticipantsMermaid(data) {
|
|
346
|
+
const nodes = (data && data.nodes) || [];
|
|
347
|
+
if (!nodes.length) return '';
|
|
348
|
+
const out = ['graph TD'];
|
|
349
|
+
let oi = 0, ai = 0;
|
|
350
|
+
nodes.forEach((n, ni) => {
|
|
351
|
+
const nid = 'N' + ni;
|
|
352
|
+
out.push(` ${nid}["🖥 ${mlbl(n.id)}${n.isLocal ? '' : ' · 🌐'}"]`);
|
|
353
|
+
(n.owners || []).forEach((o) => {
|
|
354
|
+
const oid = 'O' + (oi++);
|
|
355
|
+
const tag = o.isSelf ? ' · you' : (o.isCreator ? ' · creator' : (o.isMember ? '' : ' · guest'));
|
|
356
|
+
out.push(` ${oid}(["👤 ${mlbl(o.owner)}${tag}"])`);
|
|
357
|
+
out.push(` ${nid} --> ${oid}`);
|
|
358
|
+
(o.agents || []).forEach((a) => {
|
|
359
|
+
const aid = 'A' + (ai++);
|
|
360
|
+
out.push(` ${aid}["🤖 ${mlbl(a.name)} · ${a.contributions}"]`);
|
|
361
|
+
// Own agents get a solid edge; everyone else's a dashed edge (greyed — trace only, no live status).
|
|
362
|
+
out.push(a.isOwn ? ` ${oid} --> ${aid}` : ` ${oid} -.-> ${aid}`);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
return out.join('\n');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Activity feed for a workspace (who did what / where / draft-edit vs publish / when). */
|
|
370
|
+
export async function getWorkspaceActivity(orgId, wsId) {
|
|
371
|
+
try {
|
|
372
|
+
const r = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/activity?ws=${encodeURIComponent(wsId)}`);
|
|
373
|
+
return r?.data || { events: [], total: 0 };
|
|
374
|
+
} catch { return { events: [], total: 0 }; }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Read the manifest-driven workspace. Returns null if the workspace has no manifest yet. */
|
|
378
|
+
export async function getWorkspace(orgId, wsId) {
|
|
379
|
+
const resp = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace?ws=${encodeURIComponent(wsId)}`);
|
|
380
|
+
return resp?.data || null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Overwrite a workspace's manifest (e.g. edited name/summary/policy from Settings). */
|
|
384
|
+
export async function saveManifest(orgId, wsId, manifest) {
|
|
385
|
+
return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.manifest`, value: manifest, visibility: 'private' });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* ── Workspace registry: one organism lists its workspaces at organism.{id}.meta.workspaces =
|
|
389
|
+
* { workspaces: [{ id, name, createdAt }] }. A workspace appears here even before it has a
|
|
390
|
+
* manifest (so a freshly-created, not-yet-generated workspace is openable). ── */
|
|
391
|
+
|
|
392
|
+
/** List an organism's workspaces (registry order). */
|
|
393
|
+
export async function listWorkspaces(orgId) {
|
|
394
|
+
const key = `organism.${orgId}.meta.workspaces`;
|
|
395
|
+
try {
|
|
396
|
+
const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(key)}`);
|
|
397
|
+
const item = (resp?.data?.items || []).find(i => i.key === key);
|
|
398
|
+
return Array.isArray(item?.value?.workspaces) ? item.value.workspaces : [];
|
|
399
|
+
} catch { return []; }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Persist the workspace registry. */
|
|
403
|
+
export async function saveWorkspaceRegistry(orgId, workspaces) {
|
|
404
|
+
return apiPost('/v1/memory', { key: `organism.${orgId}.meta.workspaces`, value: { workspaces }, visibility: 'private' });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* ── Workspace access (per-workspace, creator-controlled, consent-backed) ──
|
|
408
|
+
* Organism membership lets you DISCOVER every workspace (names + creator + your access status);
|
|
409
|
+
* reading a workspace's content needs the creator's approval. ── */
|
|
410
|
+
|
|
411
|
+
/** Discover all workspaces in the org with your access status ('owner' | 'granted' | 'none'). */
|
|
412
|
+
export async function discoverWorkspaces(orgId) {
|
|
413
|
+
try {
|
|
414
|
+
const resp = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspaces`);
|
|
415
|
+
return Array.isArray(resp?.data?.workspaces) ? resp.data.workspaces : [];
|
|
416
|
+
} catch { return []; }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Request access to a workspace you can see but not read. */
|
|
420
|
+
export async function requestWorkspaceAccess(orgId, ws, message) {
|
|
421
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/workspace-access`, { ws, message: message || '' });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** List access requests for a workspace you created (creator/admin only). */
|
|
425
|
+
export async function listWorkspaceRequests(orgId, ws) {
|
|
426
|
+
try {
|
|
427
|
+
const resp = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace-access?ws=${encodeURIComponent(ws)}`);
|
|
428
|
+
return Array.isArray(resp?.data?.requests) ? resp.data.requests : [];
|
|
429
|
+
} catch { return []; }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Approve or deny a member's access request to your workspace. */
|
|
433
|
+
export async function decideWorkspaceAccess(orgId, ws, requester, decision) {
|
|
434
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/workspace-access/decision`, { ws, requester, decision });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Create a new (empty) workspace: register it, return its { id, name }. The manifest is written
|
|
438
|
+
* later by setup/generate. */
|
|
439
|
+
export async function createWorkspace(orgId, name) {
|
|
440
|
+
const id = 'ws-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
|
441
|
+
const createdBy = (currentGhii().split('@')[0]) || ''; // bare owner name — who controls this workspace's access
|
|
442
|
+
const entry = { id, name: String(name || '').trim() || 'Workspace', createdAt: new Date().toISOString(), createdBy };
|
|
443
|
+
const list = await listWorkspaces(orgId);
|
|
444
|
+
await saveWorkspaceRegistry(orgId, [...list, entry]);
|
|
445
|
+
return entry;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** kebab-case a free-typed name into a safe namespace segment / type name. */
|
|
449
|
+
function slug(name) {
|
|
450
|
+
return String(name || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'item';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ── Deterministic Mermaid charts (built from stable workspace/organism data — no AI) ──
|
|
454
|
+
|
|
455
|
+
/** Sanitise a label for a Mermaid node (strip chars that break the syntax; keep it single-line). */
|
|
456
|
+
function mlbl(s) {
|
|
457
|
+
const t = String(s == null ? '' : s).replace(/["[\]{}|<>;`\\]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
458
|
+
return (t.length > 96 ? t.slice(0, 95).replace(/\s+\S*$/, '') + '…' : t) || '—';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Chart 1 — organism dependency overview: who/what uses this organism. Async (aggregates members,
|
|
462
|
+
* agents, workspaces + their structure, and knowledge packages). Returns Mermaid source text. */
|
|
463
|
+
export async function buildOrganismOverviewMermaid(orgId) {
|
|
464
|
+
const nodeId = (currentGhii().split('@')[1]) || '';
|
|
465
|
+
const [orgResp, memResp, wsList] = await Promise.all([
|
|
466
|
+
getOrganism(orgId).catch(() => null),
|
|
467
|
+
listMembers(orgId).catch(() => null),
|
|
468
|
+
listWorkspaces(orgId).catch(() => []),
|
|
469
|
+
]);
|
|
470
|
+
const org = orgResp?.data?.organism || {};
|
|
471
|
+
const members = memResp?.data?.members || [];
|
|
472
|
+
const agents = org.agentGaiis || [];
|
|
473
|
+
const wsData = [];
|
|
474
|
+
for (const w of wsList) {
|
|
475
|
+
const ws = await getWorkspace(orgId, w.id).catch(() => null);
|
|
476
|
+
const sources = await getWorkspaceSources(orgId, w.id).catch(() => []);
|
|
477
|
+
const types = (ws?.manifest?.objectTypes || []).filter(o => o.backing === 'memory')
|
|
478
|
+
.map(o => `${o.name} (${o.mode === 'document' ? 'doc' : 'rec'})`);
|
|
479
|
+
const knowledge = sources.filter(s => s.type === 'knowledge').map(s => s.label || s.packageId);
|
|
480
|
+
wsData.push({ name: w.name || w.id, types, knowledge });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const nodes = [` ORG(["🏢 ${mlbl(org.name || orgId)}"])`];
|
|
484
|
+
const edges = [];
|
|
485
|
+
const externalIds = new Set();
|
|
486
|
+
|
|
487
|
+
if (members.length) {
|
|
488
|
+
nodes.push(' subgraph USERS["👥 Users"]', ' direction TB');
|
|
489
|
+
members.forEach((m, i) => {
|
|
490
|
+
// External = a member whose GHII names a DIFFERENT node (federated). Local members are keyed by
|
|
491
|
+
// the bare owner name (no @node), so only an explicit @other-node counts as external.
|
|
492
|
+
const isExt = !!m.ghii && m.ghii.includes('@') && !!nodeId && !m.ghii.endsWith('@' + nodeId);
|
|
493
|
+
if (isExt) externalIds.add('U' + i);
|
|
494
|
+
nodes.push(` U${i}["👤 ${mlbl(m.ghii)}${m.role ? ' · ' + mlbl(m.role) : ''}${isExt ? ' · 🌐 external' : ''}"]`);
|
|
495
|
+
});
|
|
496
|
+
nodes.push(' end');
|
|
497
|
+
members.forEach((m, i) => edges.push(externalIds.has('U' + i) ? ` U${i} -. consent .-> ORG` : ` U${i} --> ORG`));
|
|
498
|
+
}
|
|
499
|
+
if (agents.length) {
|
|
500
|
+
nodes.push(' subgraph AGENTS["🤖 Agents"]', ' direction TB');
|
|
501
|
+
agents.forEach((a, i) => nodes.push(` A${i}["🤖 ${mlbl(a)}"]`));
|
|
502
|
+
nodes.push(' end');
|
|
503
|
+
agents.forEach((a, i) => edges.push(` A${i} --> ORG`));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const kmap = new Map(); // knowledge label -> node id
|
|
507
|
+
for (const w of wsData) for (const k of w.knowledge) if (!kmap.has(k)) kmap.set(k, 'K' + kmap.size);
|
|
508
|
+
if (kmap.size) {
|
|
509
|
+
nodes.push(' subgraph KNOWLEDGE["📚 Knowledge packages"]', ' direction TB');
|
|
510
|
+
for (const [label, kid] of kmap) nodes.push(` ${kid}["📚 ${mlbl(label)}"]`);
|
|
511
|
+
nodes.push(' end');
|
|
512
|
+
}
|
|
513
|
+
if (wsData.length) {
|
|
514
|
+
nodes.push(' subgraph WORKSPACES["🗂️ Workspaces"]', ' direction TB');
|
|
515
|
+
wsData.forEach((w, i) => {
|
|
516
|
+
nodes.push(` W${i}["🗂️ ${mlbl(w.name)}"]`);
|
|
517
|
+
if (w.types.length) nodes.push(` W${i}S["${mlbl(w.types.join(' · '))}"]`);
|
|
518
|
+
});
|
|
519
|
+
nodes.push(' end');
|
|
520
|
+
wsData.forEach((w, i) => {
|
|
521
|
+
edges.push(` ORG --> W${i}`);
|
|
522
|
+
if (w.types.length) edges.push(` W${i} --> W${i}S`);
|
|
523
|
+
for (const k of w.knowledge) edges.push(` W${i} -. uses .-> ${kmap.get(k)}`);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (!members.length && !agents.length && !wsData.length) {
|
|
527
|
+
edges.push(' EMPTY["No members, agents or workspaces yet"] --> ORG');
|
|
528
|
+
}
|
|
529
|
+
return ['graph LR', ...nodes, ...edges].join('\n');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Chart 2 — the edit→publish lifecycle this workspace's manifest defines (deterministic). Records
|
|
533
|
+
* are schema-validated; the publish gate + the manifest's policy.alwaysGate add a review step. */
|
|
534
|
+
export function buildEditFlowMermaid(manifest, gateOn) {
|
|
535
|
+
const types = (manifest?.objectTypes || []).filter(o => o.backing === 'memory');
|
|
536
|
+
const recTypes = types.filter(o => o.mode !== 'document').map(o => o.name);
|
|
537
|
+
const docTypes = types.filter(o => o.mode === 'document').map(o => o.name);
|
|
538
|
+
const alwaysGate = (manifest?.policy && manifest.policy.alwaysGate) || [];
|
|
539
|
+
|
|
540
|
+
const L = ['flowchart LR'];
|
|
541
|
+
L.push(' START(["Pick what to edit"])');
|
|
542
|
+
if (recTypes.length) L.push(` REC["📋 Records: ${mlbl(recTypes.join(', '))} · schema form"]`);
|
|
543
|
+
if (docTypes.length) L.push(` DOC["📄 Documents: ${mlbl(docTypes.join(', '))} · free-form markdown"]`);
|
|
544
|
+
L.push(' DRAFT["✏️ Save as DRAFT · working copy"]');
|
|
545
|
+
if (gateOn) L.push(' GATE{"🔍 Owner review · publish gate on"}');
|
|
546
|
+
L.push(' PUB["✅ Publish"]');
|
|
547
|
+
L.push(' VER["📌 .version.N + .latest"]');
|
|
548
|
+
|
|
549
|
+
if (recTypes.length) L.push(' START --> REC --> DRAFT');
|
|
550
|
+
if (docTypes.length) L.push(' START --> DOC --> DRAFT');
|
|
551
|
+
if (!recTypes.length && !docTypes.length) L.push(' START --> DRAFT');
|
|
552
|
+
if (gateOn) { L.push(' DRAFT --> GATE'); L.push(' GATE -- approve --> PUB'); L.push(' GATE -- reject --> DRAFT'); }
|
|
553
|
+
else L.push(' DRAFT --> PUB');
|
|
554
|
+
L.push(' PUB --> VER');
|
|
555
|
+
L.push(' VER -. edit again .-> DRAFT');
|
|
556
|
+
if (alwaysGate.length) { L.push(` NOTE["⚠️ Always needs approval: ${mlbl(alwaysGate.join(', '))}"]`); L.push(` NOTE -.-> ${gateOn ? 'GATE' : 'PUB'}`); }
|
|
557
|
+
return L.join('\n');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ── Access prompt: a copy-paste prompt teaching an AI/agent how to use THIS workspace ──
|
|
561
|
+
// Bridges the MCP gap (no workspace-aware tools yet) by injecting the real structure + the exact
|
|
562
|
+
// conventions. Two variants: 'human' (paste into a chat) and 'agent' (imperative, assumes tools).
|
|
563
|
+
|
|
564
|
+
/** Format one objectType's full schema for the prompt's STRUCTURE block. */
|
|
565
|
+
function describeType(ot, schema) {
|
|
566
|
+
if (ot.mode === 'document') {
|
|
567
|
+
return `• ${ot.name} (document) — namespace "${ot.namespace}". Free-form markdown pages { id, title, markdown }, organised into sections (read organism.{id}.w.{ws}.meta.sections.${ot.name}).`;
|
|
568
|
+
}
|
|
569
|
+
const props = (schema && schema.properties) || {};
|
|
570
|
+
const req = new Set((schema && schema.required) || []);
|
|
571
|
+
const fields = Object.entries(props).map(([k, d]) => {
|
|
572
|
+
const bits = [d.type || 'string'];
|
|
573
|
+
if (req.has(k)) bits.push('required');
|
|
574
|
+
if (Array.isArray(d.enum)) bits.push('enum: ' + d.enum.join(' | '));
|
|
575
|
+
if (d.type === 'array' && d.items?.type) bits.push('of ' + d.items.type);
|
|
576
|
+
return ` - ${k} (${bits.join(', ')})`;
|
|
577
|
+
});
|
|
578
|
+
return `• ${ot.name} (records) — namespace "${ot.namespace}", writeRole ${ot.writeRole || 'member'}. Fields:\n${fields.join('\n') || ' (no schema)'}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Build the workspace-access prompt for an AI/agent. variant: 'human' | 'agent'. Async — fetches
|
|
582
|
+
* each records-type's schema so the FULL field list is inlined. */
|
|
583
|
+
export async function buildAccessPrompt(orgId, orgName, wsId, ws, variant = 'human') {
|
|
584
|
+
const nodeUrl = window.location.origin;
|
|
585
|
+
const m = ws?.manifest || {};
|
|
586
|
+
const wsName = m.name || wsId;
|
|
587
|
+
const types = (m.objectTypes || []).filter(ot => ot.backing === 'memory');
|
|
588
|
+
const described = [];
|
|
589
|
+
for (const ot of types) {
|
|
590
|
+
// records is the default mode (mode may be undefined) — fetch the schema unless it's a document.
|
|
591
|
+
const schema = ot.mode !== 'document' ? await getObjectSchema(orgId, wsId, ot.namespace).catch(() => null) : null;
|
|
592
|
+
described.push(describeType(ot, schema));
|
|
593
|
+
}
|
|
594
|
+
const structure = described.join('\n') || '(no spaces declared yet)';
|
|
595
|
+
|
|
596
|
+
const access = [
|
|
597
|
+
`- Read the manifest: aimeat_memory_read key="organism.${orgId}.w.${wsId}.meta.manifest"`,
|
|
598
|
+
`- List everything: aimeat_memory_list prefix="organism.${orgId}.w.${wsId}." limit=500`,
|
|
599
|
+
` (keys end in .draft = working copy, .latest = published, .version.N = history)`,
|
|
600
|
+
`- Document sections: aimeat_memory_read key="organism.${orgId}.w.${wsId}.meta.sections.{type}"`,
|
|
601
|
+
`- Write/refresh a draft: aimeat_memory_write key="organism.${orgId}.w.${wsId}.{namespace}.{id}.draft" value={...}`,
|
|
602
|
+
`- Attach a file/screenshot: aimeat_storage_upload key="organism.${orgId}.w.${wsId}.img.{name}"`,
|
|
603
|
+
` then embed it in a document's markdown as `,
|
|
604
|
+
`- Publish a draft: POST ${nodeUrl}/v1/organisms/${orgId}/publish body { "ws":"${wsId}", "namespace":"...", "id":"..." }`,
|
|
605
|
+
` (snapshots .version.N + .latest, consumes the draft; may require operator approval if the publish gate is on)`,
|
|
606
|
+
].join('\n');
|
|
607
|
+
|
|
608
|
+
const intents = [
|
|
609
|
+
` (a) Status update — read .latest, refresh a status document, publish it.`,
|
|
610
|
+
` (b) Manage tasks / deliverables — add or edit drafts in a records space, publish when ready.`,
|
|
611
|
+
` (c) Be a coding agent — pull specs + tasks from here, implement them, then update the task`,
|
|
612
|
+
` records' status + the related documents + the status page as you go.`,
|
|
613
|
+
].join('\n');
|
|
614
|
+
|
|
615
|
+
if (variant === 'agent') {
|
|
616
|
+
return [
|
|
617
|
+
`TASK: operate an AIMEAT organism workspace. Connect, learn it, interview the operator, then act.`,
|
|
618
|
+
``,
|
|
619
|
+
`CONNECTION`,
|
|
620
|
+
`- Node: ${nodeUrl}`,
|
|
621
|
+
`- Organism: "${orgName || orgId}" id: ${orgId}`,
|
|
622
|
+
`- Workspace: "${wsName}" ws: ${wsId}`,
|
|
623
|
+
`- Use the AIMEAT MCP tools (aimeat_memory_*, aimeat_storage_*) + the REST publish endpoint below.`,
|
|
624
|
+
` If AIMEAT tools are unavailable, STOP and report — do not invent data.`,
|
|
625
|
+
``,
|
|
626
|
+
`STEP 1 — LEARN (do before acting; then summarise the structure back):`,
|
|
627
|
+
access,
|
|
628
|
+
``,
|
|
629
|
+
`STRUCTURE (objectTypes — full schema):`,
|
|
630
|
+
structure,
|
|
631
|
+
``,
|
|
632
|
+
`STEP 2 — INTERVIEW the operator (ask, don't assume) which of:`,
|
|
633
|
+
intents,
|
|
634
|
+
` Get specifics: which space, what to change, definition of done.`,
|
|
635
|
+
``,
|
|
636
|
+
`STEP 3 — ACT (only after the operator confirms). Records: validate against the schema above`,
|
|
637
|
+
`before writing. Keep edits as DRAFTS unless told to publish. The status page is just a document —`,
|
|
638
|
+
`rewrite its markdown and publish.`,
|
|
639
|
+
``,
|
|
640
|
+
`RULES: never publish without the operator's OK unless told to run autonomously; re-read .latest`,
|
|
641
|
+
`before overwriting (avoid clobbering a newer update); report which keys you wrote and what changed.`,
|
|
642
|
+
].join('\n');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return [
|
|
646
|
+
`I'm using an AIMEAT organism workspace and I'd like your help with it. First LEARN its structure,`,
|
|
647
|
+
`then ASK me what I want to do, then help me do it.`,
|
|
648
|
+
``,
|
|
649
|
+
`CONNECTION`,
|
|
650
|
+
`- Node: ${nodeUrl}`,
|
|
651
|
+
`- Organism: "${orgName || orgId}" (id: ${orgId})`,
|
|
652
|
+
`- Workspace: "${wsName}" (ws: ${wsId})`,
|
|
653
|
+
`If you're connected to AIMEAT (its MCP tools), you can read and write this workspace directly.`,
|
|
654
|
+
`If you're not connected, tell me and I'll paste content back and forth manually.`,
|
|
655
|
+
``,
|
|
656
|
+
`WHAT THIS IS`,
|
|
657
|
+
`A workspace is a set of "spaces". Each space is either a DOCUMENT space (a free-form wiki: markdown`,
|
|
658
|
+
`pages in sections) or a RECORDS space (a schema-locked list, like a form). Items have a working`,
|
|
659
|
+
`DRAFT and, once published, a LATEST version (with history).`,
|
|
660
|
+
``,
|
|
661
|
+
`STRUCTURE (read this back to me so I know you understand it):`,
|
|
662
|
+
structure,
|
|
663
|
+
``,
|
|
664
|
+
`HOW TO ACCESS IT (AIMEAT MCP + REST):`,
|
|
665
|
+
access,
|
|
666
|
+
``,
|
|
667
|
+
`WHAT I MIGHT WANT (ask me which):`,
|
|
668
|
+
intents,
|
|
669
|
+
``,
|
|
670
|
+
`RULES: keep changes as drafts so I can review; don't publish without my OK; re-read .latest before`,
|
|
671
|
+
`overwriting so you don't clobber a newer update; tell me which keys you wrote and what changed.`,
|
|
672
|
+
``,
|
|
673
|
+
`Start by reading the manifest + structure, then ask me which of (a)/(b)/(c) and the specifics.`,
|
|
674
|
+
].join('\n');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Manually add an object type to the manifest (no AI). mode 'document' needs no schema;
|
|
678
|
+
* 'records' gets a starter {id,title} schema that can be refined later via Restructure. */
|
|
679
|
+
export async function addSpace(orgId, wsId, manifest, name, mode) {
|
|
680
|
+
const base = slug(name);
|
|
681
|
+
const plural = base.endsWith('s') ? base : base + 's';
|
|
682
|
+
const existing = new Set((manifest.objectTypes || []).map(o => o.namespace));
|
|
683
|
+
let namespace = `shared.${plural}`;
|
|
684
|
+
for (let i = 2; existing.has(namespace); i++) namespace = `shared.${plural}-${i}`;
|
|
685
|
+
const ot = { name: base, schemaRef: `schema:${base}@1`, namespace, backing: 'memory', writeRole: 'member', cardinality: 'many', versioned: true, mode };
|
|
686
|
+
if (mode === 'records') {
|
|
687
|
+
await apiPut(`/v1/memory/${encodeURIComponent(`${wsRoot(orgId, wsId)}.${namespace}`)}/schema`, {
|
|
688
|
+
schema: { type: 'object', required: ['id', 'title'], properties: { id: { type: 'string' }, title: { type: 'string' } } },
|
|
689
|
+
apply_to: 'prefix', schema_mode: 'strict',
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return saveManifest(orgId, wsId, { ...manifest, objectTypes: [...(manifest.objectTypes || []), ot] });
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Remove an object type from the manifest by name. Its data is left in memory (orphaned, not
|
|
696
|
+
* deleted) — re-adding the type surfaces it again; a full wipe is the workspace-delete path. */
|
|
697
|
+
export async function removeSpace(orgId, wsId, manifest, typeName) {
|
|
698
|
+
return saveManifest(orgId, wsId, { ...manifest, objectTypes: (manifest.objectTypes || []).filter(o => o.name !== typeName) });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** Fetch the JSON Schema registered for an object-type namespace (drives schema-aware forms).
|
|
702
|
+
* Probes a sub-key so the prefix schema resolves (a prefix schema doesn't self-match its own key). */
|
|
703
|
+
export async function getObjectSchema(orgId, wsId, namespace) {
|
|
704
|
+
const key = `${wsRoot(orgId, wsId)}.${namespace}._form`;
|
|
705
|
+
try {
|
|
706
|
+
const resp = await apiGet(`/v1/memory/${encodeURIComponent(key)}/schema`);
|
|
707
|
+
return resp?.data?.has_schema ? resp.data.schema : null;
|
|
708
|
+
} catch { return null; }
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** Write/overwrite an object's draft (`…w.{wsId}.{namespace}.{id}.draft`). */
|
|
712
|
+
export async function writeDraft(orgId, wsId, namespace, instanceId, value) {
|
|
713
|
+
return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.${namespace}.${instanceId}.draft`, value, visibility: 'private' });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** Publish a draft → new version + latest (or a pending approval if the publish gate is on). */
|
|
717
|
+
export async function publishDraft(orgId, wsId, namespace, instanceId) {
|
|
718
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/publish`, { ws: wsId, namespace, id: instanceId });
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** Delete one workspace object (record or document) — its draft, published .latest and all
|
|
722
|
+
* .version.N history. Returns the number of keys removed. */
|
|
723
|
+
export async function deleteWorkspaceObject(orgId, wsId, namespace, id) {
|
|
724
|
+
const base = `${wsRoot(orgId, wsId)}.${namespace}.${id}`;
|
|
725
|
+
const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(base + '.')}`);
|
|
726
|
+
const items = (resp?.data?.items) || [];
|
|
727
|
+
let deleted = 0;
|
|
728
|
+
for (const it of items) {
|
|
729
|
+
const role = it.key.slice(base.length + 1);
|
|
730
|
+
if (role === 'draft' || role === 'latest' || /^version\.\d+$/.test(role)) {
|
|
731
|
+
try { await apiDelete(`/v1/memory/${encodeURIComponent(it.key)}`); deleted++; } catch { /* skip */ }
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return deleted;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** List pending approvals (the gate inbox). */
|
|
738
|
+
export async function listApprovals(orgId, status = 'pending') {
|
|
739
|
+
const resp = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/approvals${status ? `?status=${status}` : ''}`);
|
|
740
|
+
return resp?.data?.approvals || [];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** Resolve an approval (approve | reject | edit). */
|
|
744
|
+
export async function resolveApproval(orgId, aid, decision, note) {
|
|
745
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/approvals/${encodeURIComponent(aid)}`, { decision, note });
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Read the runtime config entry (publish gate toggle, etc.). Uses prefix-list to avoid 404 noise. */
|
|
749
|
+
export async function getConfig(orgId) {
|
|
750
|
+
const key = `organism.${orgId}.meta.config`;
|
|
751
|
+
try {
|
|
752
|
+
const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(key)}`);
|
|
753
|
+
return (resp?.data?.items || []).find(i => i.key === key)?.value || {};
|
|
754
|
+
} catch { return {}; }
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/** ── Document images (stored in /v1/storage, fetched with the session for display) ── */
|
|
758
|
+
function blobToBase64(blob) {
|
|
759
|
+
return new Promise((resolve, reject) => {
|
|
760
|
+
const r = new FileReader();
|
|
761
|
+
r.onload = () => resolve(String(r.result).split(',')[1] || '');
|
|
762
|
+
r.onerror = reject;
|
|
763
|
+
r.readAsDataURL(blob);
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** Full data: URL (for immediate display in the editor before the storage upload finishes). */
|
|
768
|
+
export function blobToDataUrl(blob) {
|
|
769
|
+
return new Promise((resolve, reject) => {
|
|
770
|
+
const r = new FileReader();
|
|
771
|
+
r.onload = () => resolve(String(r.result));
|
|
772
|
+
r.onerror = reject;
|
|
773
|
+
r.readAsDataURL(blob);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** Upload an image blob to the organism's private storage. Returns a /v1/storage/<key> URL to
|
|
778
|
+
* embed in markdown; the document view resolves it with the session token (storage GET needs auth). */
|
|
779
|
+
export async function uploadImage(orgId, blob, mime) {
|
|
780
|
+
const ext = (mime && mime.split('/')[1]) ? '.' + mime.split('/')[1].replace(/[^a-z0-9]/gi, '') : '';
|
|
781
|
+
const key = `organism.${orgId}.img.${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}${ext}`;
|
|
782
|
+
const resp = await apiPost('/v1/storage', { key, visibility: 'private', data: await blobToBase64(blob), mime_type: mime || 'application/octet-stream' });
|
|
783
|
+
if (resp?.ok === false) throw new Error(resp?.error?.message || 'Upload failed');
|
|
784
|
+
return `/v1/storage/${encodeURIComponent(resp?.data?.key || key)}`;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** Fetch a /v1/storage file with the session token and return an object URL (for <img>). */
|
|
788
|
+
export async function fetchStorageObjectUrl(url) {
|
|
789
|
+
const jwt = window.AIMEAT?.auth?.getSession?.()?.jwt;
|
|
790
|
+
const resp = await fetch(url, { headers: jwt ? { Authorization: 'Bearer ' + jwt } : {} });
|
|
791
|
+
if (!resp.ok) throw new Error('image fetch failed: ' + resp.status);
|
|
792
|
+
return URL.createObjectURL(await resp.blob());
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/** List the caller's storage files → map of key → visibility (for showing per-image visibility). */
|
|
796
|
+
export async function listStorageVisibilities() {
|
|
797
|
+
const resp = await apiGet('/v1/storage');
|
|
798
|
+
const out = {};
|
|
799
|
+
for (const f of (resp?.data?.files || [])) out[f.key] = f.visibility;
|
|
800
|
+
return out;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** Change one stored image's visibility ('private' | 'owner' | 'public'). */
|
|
804
|
+
export async function setImageVisibility(key, visibility) {
|
|
805
|
+
return apiPatch(`/v1/storage/${encodeURIComponent(key)}/visibility`, { visibility });
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Matches an embedded storage image in either URL form, capturing the bare object key in group 3:
|
|
809
|
+
//  — private, owner fetches with the session token
|
|
810
|
+
//  — public, anyone loads it via a plain <img>
|
|
811
|
+
const STORAGE_IMG_RE = /!\[([^\]]*)\]\(\/v1\/(?:storage|pub\/[^/)]+)\/([^\s)]+)\)/g;
|
|
812
|
+
|
|
813
|
+
/** The current owner's GHII (`owner@node`) — used to build public /v1/pub image URLs. */
|
|
814
|
+
export function currentGhii() {
|
|
815
|
+
return window.AIMEAT?.auth?.getSession?.()?.ghii || '';
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** Pull the storage object keys (+ alt text) embedded in a markdown document, in order (both forms). */
|
|
819
|
+
export function extractStorageImages(markdown) {
|
|
820
|
+
const out = []; const seen = new Set();
|
|
821
|
+
STORAGE_IMG_RE.lastIndex = 0;
|
|
822
|
+
let m;
|
|
823
|
+
while ((m = STORAGE_IMG_RE.exec(String(markdown || ''))) !== null) {
|
|
824
|
+
const key = decodeURIComponent(m[2]);
|
|
825
|
+
if (seen.has(key)) continue;
|
|
826
|
+
seen.add(key);
|
|
827
|
+
out.push({ key, alt: m[1] || key.split('.').pop() });
|
|
828
|
+
}
|
|
829
|
+
return out;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/** Rewrite each embedded image's URL to match its visibility: public → /v1/pub/<ghii>/<key> (loads
|
|
833
|
+
* for any viewer), otherwise → /v1/storage/<key> (owner-only, session-fetched). `visByKey` maps the
|
|
834
|
+
* bare object key → 'public' | 'private' | 'owner'; keys not present default to private. */
|
|
835
|
+
export function applyImageVisibilityUrls(markdown, visByKey, ghii) {
|
|
836
|
+
return String(markdown || '').replace(STORAGE_IMG_RE, (full, alt, rawKey) => {
|
|
837
|
+
const key = decodeURIComponent(rawKey);
|
|
838
|
+
const url = (visByKey[key] === 'public' && ghii)
|
|
839
|
+
? `/v1/pub/${encodeURIComponent(ghii)}/${encodeURIComponent(key)}`
|
|
840
|
+
: `/v1/storage/${encodeURIComponent(key)}`;
|
|
841
|
+
return ``;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** Section indexes for every document-space, keyed by objectType name. A section is
|
|
846
|
+
* { id, name, parentId, documents:[docId] } — a flat array forming a tree via parentId. */
|
|
847
|
+
export async function getAllSections(orgId, wsId) {
|
|
848
|
+
const out = {};
|
|
849
|
+
try {
|
|
850
|
+
const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(`${wsRoot(orgId, wsId)}.meta.sections.`)}`);
|
|
851
|
+
for (const it of (resp?.data?.items || [])) {
|
|
852
|
+
const m = String(it.key || '').match(/\.meta\.sections\.(.+)$/);
|
|
853
|
+
if (m) out[m[1]] = Array.isArray(it.value?.sections) ? it.value.sections : [];
|
|
854
|
+
}
|
|
855
|
+
} catch { /* none yet */ }
|
|
856
|
+
return out;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** Persist the section index for one document-space (…w.{wsId}.meta.sections.{typeName}). */
|
|
860
|
+
export async function saveSections(orgId, wsId, typeName, sections) {
|
|
861
|
+
return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.sections.${typeName}`, value: { sections }, visibility: 'private' });
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/* ── Sources: references the workspace draws on (memory / storage / knowledge). Pointers only —
|
|
865
|
+
* the referenced data is never copied or moved, it stays where it lives. Stored at
|
|
866
|
+
* organism.{id}.meta.sources = { sources: [{ id, type, label, key?, ownerGaii?, packageId?, external }] }. */
|
|
867
|
+
|
|
868
|
+
/** Read the workspace's attached source references (empty array if none). */
|
|
869
|
+
export async function getWorkspaceSources(orgId, wsId) {
|
|
870
|
+
const key = `${wsRoot(orgId, wsId)}.meta.sources`;
|
|
871
|
+
try {
|
|
872
|
+
const resp = await apiGet(`/v1/memory?prefix=${encodeURIComponent(key)}`);
|
|
873
|
+
const item = (resp?.data?.items || []).find(i => i.key === key);
|
|
874
|
+
return Array.isArray(item?.value?.sources) ? item.value.sources : [];
|
|
875
|
+
} catch { return []; }
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** Persist the workspace's source references. */
|
|
879
|
+
export async function saveWorkspaceSources(orgId, wsId, sources) {
|
|
880
|
+
return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.sources`, value: { sources }, visibility: 'private' });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** List the caller's own storage files (key, size, mime, visibility) — for the storage source picker. */
|
|
884
|
+
export async function listOwnStorageFiles() {
|
|
885
|
+
const resp = await apiGet('/v1/storage');
|
|
886
|
+
return resp?.data?.files || [];
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** Toggle the publish-review gate in the org's config. */
|
|
890
|
+
export async function setPublishGate(orgId, enabled, approverRole = 'owner') {
|
|
891
|
+
const cfg = await getConfig(orgId);
|
|
892
|
+
const next = { ...cfg, gates: { ...(cfg.gates || {}), publish: { enabled: !!enabled, approverRole } } };
|
|
893
|
+
return apiPost('/v1/memory', { key: `organism.${orgId}.meta.config`, value: next, visibility: 'private' });
|
|
894
|
+
}
|