aimeat 1.33.2 → 1.34.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/dist/locales/en.json +388 -14
- package/dist/locales/fi.json +388 -14
- package/dist/public/cortex-bundled/aimeat-charts.js +3 -3
- package/dist/public/cortex-bundled/aimeat-charts.yaml +3 -3
- package/dist/public/cortex-bundled/aimeat-i18n.js +123 -0
- package/dist/public/cortex-bundled/aimeat-i18n.yaml +64 -0
- package/dist/public/css/views/landing.css +2 -0
- package/dist/public/css/views/secretary.css +412 -2
- package/dist/public/js/services/apps.js +4 -2
- package/dist/public/js/services/memory.js +9 -3
- package/dist/public/js/services/offers.js +1 -1
- package/dist/public/js/services/organisms.js +49 -17
- package/dist/public/js/services/secretary-helpers.js +262 -17
- package/dist/public/js/services/secretary-policy.js +1 -0
- package/dist/public/js/services/secretary-rules.js +70 -0
- package/dist/public/js/theme.js +20 -0
- package/dist/public/lib/VENDORED-STYLING.md +31 -0
- package/dist/public/lib/aimeat-daisyui-bridge.css +48 -0
- package/dist/public/lib/chartjs@4.js +14 -0
- package/dist/public/lib/daisyui@5.css +7 -0
- package/dist/public/lib/tailwindcss@4.js +8 -0
- package/dist/public/llms-template.txt +9 -6
- package/dist/public/showroom.html +104 -0
- package/dist/public/showroom.js +247 -0
- package/dist/public/spa.html +18 -16
- package/dist/public/views/aimeat-os.js +3 -2
- package/dist/public/views/landing.js +50 -6
- package/dist/public/views/profile/agents/agent-card.js +7 -0
- package/dist/public/views/profile/agents/state-detector.js +6 -0
- package/dist/public/views/profile/agents/tab-integration.js +6 -0
- package/dist/public/views/profile/apps-tab.js +46 -0
- package/dist/public/views/profile/extensions-tab.js +4 -0
- package/dist/public/views/profile/inbox-tab.js +1 -1
- package/dist/public/views/profile/landing-page.js +8 -3
- package/dist/public/views/profile/memory-tab.js +37 -6
- package/dist/public/views/profile/organisms/home.js +32 -0
- package/dist/public/views/profile/organisms/workspace-list.js +36 -5
- package/dist/public/views/profile/organisms/workspace.js +29 -4
- package/dist/public/views/profile/organisms-tab.js +61 -8
- package/dist/public/views/secretary/calendar.js +327 -0
- package/dist/public/views/secretary/cards-reach.js +16 -17
- package/dist/public/views/secretary/cards.js +173 -66
- package/dist/public/views/secretary/dashboard.js +551 -0
- package/dist/public/views/secretary/quality.js +156 -0
- package/dist/public/views/secretary/use-autonomy.js +17 -2
- package/dist/public/views/secretary/use-freshness.js +50 -0
- package/dist/public/views/secretary/use-intake.js +23 -4
- package/dist/public/views/secretary/use-layout.js +122 -0
- package/dist/public/views/secretary/use-quick-actions.js +98 -0
- package/dist/public/views/secretary/use-specialists.js +103 -0
- package/dist/public/views/secretary/use-triggers.js +159 -0
- package/dist/public/views/secretary/use-whats-next.js +340 -0
- package/dist/public/views/secretary/workflow-design.js +112 -0
- package/dist/public/views/secretary.js +663 -39
- package/dist/scripts/check-no-max-tokens.d.ts +2 -0
- package/dist/scripts/check-no-max-tokens.d.ts.map +1 -0
- package/dist/scripts/check-no-max-tokens.js +71 -0
- package/dist/scripts/check-no-max-tokens.js.map +1 -0
- package/dist/src/data/aimeat-iam-package.d.ts +25 -0
- package/dist/src/data/aimeat-iam-package.d.ts.map +1 -0
- package/dist/src/data/aimeat-iam-package.js +312 -0
- package/dist/src/data/aimeat-iam-package.js.map +1 -0
- package/dist/src/data/aimeat-marketplace-package.d.ts +25 -0
- package/dist/src/data/aimeat-marketplace-package.d.ts.map +1 -0
- package/dist/src/data/aimeat-marketplace-package.js +231 -0
- package/dist/src/data/aimeat-marketplace-package.js.map +1 -0
- package/dist/src/data/app-templates.d.ts +48 -0
- package/dist/src/data/app-templates.d.ts.map +1 -0
- package/dist/src/data/app-templates.js +856 -0
- package/dist/src/data/app-templates.js.map +1 -0
- package/dist/src/data/example-packages.d.ts.map +1 -1
- package/dist/src/data/example-packages.js +3 -1
- package/dist/src/data/example-packages.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +380 -5
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +10 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +7 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +205 -2
- package/dist/src/generated/prisma-postgres/index.js +10 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +23 -13
- package/dist/src/generated/prisma-postgres/wasm.js +10 -3
- package/dist/src/mcp/annotations.d.ts.map +1 -1
- package/dist/src/mcp/annotations.js +1 -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 +16 -1
- package/dist/src/mcp/catalog/definitions.js.map +1 -1
- package/dist/src/mcp/catalog/surfaces.js +3 -3
- package/dist/src/mcp/catalog/surfaces.js.map +1 -1
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js +11 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/organisms.d.ts.map +1 -1
- package/dist/src/mcp/organisms.js +48 -8
- package/dist/src/mcp/organisms.js.map +1 -1
- package/dist/src/mcp/workspaces.d.ts.map +1 -1
- package/dist/src/mcp/workspaces.js +6 -4
- package/dist/src/mcp/workspaces.js.map +1 -1
- package/dist/src/models/offer-schemas.d.ts +8 -8
- package/dist/src/routes/ai.d.ts +8 -4
- package/dist/src/routes/ai.d.ts.map +1 -1
- package/dist/src/routes/ai.js +38 -14
- package/dist/src/routes/ai.js.map +1 -1
- package/dist/src/routes/app-grants.d.ts +2 -0
- package/dist/src/routes/app-grants.d.ts.map +1 -1
- package/dist/src/routes/app-grants.js +7 -1
- package/dist/src/routes/app-grants.js.map +1 -1
- package/dist/src/routes/app-templates.d.ts +16 -0
- package/dist/src/routes/app-templates.d.ts.map +1 -0
- package/dist/src/routes/app-templates.js +40 -0
- package/dist/src/routes/app-templates.js.map +1 -0
- package/dist/src/routes/apps.d.ts.map +1 -1
- package/dist/src/routes/apps.js +55 -5
- package/dist/src/routes/apps.js.map +1 -1
- package/dist/src/routes/auth.d.ts +3 -0
- package/dist/src/routes/auth.d.ts.map +1 -1
- package/dist/src/routes/auth.js +9 -4
- package/dist/src/routes/auth.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/generator.d.ts.map +1 -1
- package/dist/src/routes/generator.js +3 -2
- package/dist/src/routes/generator.js.map +1 -1
- package/dist/src/routes/lib-ai.d.ts +0 -1
- package/dist/src/routes/lib-ai.d.ts.map +1 -1
- package/dist/src/routes/lib-ai.js +13 -6
- package/dist/src/routes/lib-ai.js.map +1 -1
- package/dist/src/routes/lib-header.d.ts +2 -0
- package/dist/src/routes/lib-header.d.ts.map +1 -1
- package/dist/src/routes/lib-header.js +7 -16
- package/dist/src/routes/lib-header.js.map +1 -1
- package/dist/src/routes/lib-markdown.d.ts +18 -0
- package/dist/src/routes/lib-markdown.d.ts.map +1 -0
- package/dist/src/routes/lib-markdown.js +236 -0
- package/dist/src/routes/lib-markdown.js.map +1 -0
- package/dist/src/routes/libs.d.ts +22 -0
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +133 -10
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/memory.d.ts.map +1 -1
- package/dist/src/routes/memory.js +21 -2
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/openrouter.d.ts +4 -0
- package/dist/src/routes/openrouter.d.ts.map +1 -1
- package/dist/src/routes/openrouter.js +17 -0
- package/dist/src/routes/openrouter.js.map +1 -1
- package/dist/src/routes/organisms.d.ts.map +1 -1
- package/dist/src/routes/organisms.js +107 -7
- package/dist/src/routes/organisms.js.map +1 -1
- package/dist/src/routes/portal.d.ts +9 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +1 -1
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/public-stats.d.ts +3 -0
- package/dist/src/routes/public-stats.d.ts.map +1 -1
- package/dist/src/routes/public-stats.js +9 -2
- package/dist/src/routes/public-stats.js.map +1 -1
- package/dist/src/routes/secretary.d.ts +29 -0
- package/dist/src/routes/secretary.d.ts.map +1 -0
- package/dist/src/routes/secretary.js +220 -0
- package/dist/src/routes/secretary.js.map +1 -0
- package/dist/src/routes/specialists.d.ts.map +1 -1
- package/dist/src/routes/specialists.js +21 -0
- package/dist/src/routes/specialists.js.map +1 -1
- package/dist/src/routes/subdomains.js +1 -1
- package/dist/src/routes/subdomains.js.map +1 -1
- package/dist/src/routes/workflows.d.ts +9 -3
- package/dist/src/routes/workflows.d.ts.map +1 -1
- package/dist/src/routes/workflows.js +9 -3
- package/dist/src/routes/workflows.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.map +1 -1
- package/dist/src/server-bootstrap/service-init.js +7 -0
- package/dist/src/server-bootstrap/service-init.js.map +1 -1
- package/dist/src/server-bootstrap/static-files.d.ts +4 -0
- package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
- package/dist/src/server-bootstrap/static-files.js +18 -0
- package/dist/src/server-bootstrap/static-files.js.map +1 -1
- package/dist/src/services/archive.d.ts +74 -0
- package/dist/src/services/archive.d.ts.map +1 -0
- package/dist/src/services/archive.js +132 -0
- package/dist/src/services/archive.js.map +1 -0
- package/dist/src/services/cortex-seeder.d.ts +4 -1
- package/dist/src/services/cortex-seeder.d.ts.map +1 -1
- package/dist/src/services/cortex-seeder.js +25 -6
- package/dist/src/services/cortex-seeder.js.map +1 -1
- package/dist/src/services/directory.d.ts.map +1 -1
- package/dist/src/services/directory.js +3 -2
- package/dist/src/services/directory.js.map +1 -1
- package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
- package/dist/src/services/generator-prompt-seeds.js +5 -4
- package/dist/src/services/generator-prompt-seeds.js.map +1 -1
- package/dist/src/services/message-send.d.ts.map +1 -1
- package/dist/src/services/message-send.js +14 -0
- package/dist/src/services/message-send.js.map +1 -1
- package/dist/src/services/organism-search.d.ts +6 -2
- package/dist/src/services/organism-search.d.ts.map +1 -1
- package/dist/src/services/organism-search.js +2 -2
- package/dist/src/services/organism-search.js.map +1 -1
- package/dist/src/services/owner-memory.d.ts +1 -0
- package/dist/src/services/owner-memory.d.ts.map +1 -1
- package/dist/src/services/owner-memory.js.map +1 -1
- package/dist/src/services/owner-session.d.ts +1 -0
- package/dist/src/services/owner-session.d.ts.map +1 -1
- package/dist/src/services/owner-session.js +10 -2
- package/dist/src/services/owner-session.js.map +1 -1
- package/dist/src/services/package-seeder.d.ts +22 -0
- package/dist/src/services/package-seeder.d.ts.map +1 -0
- package/dist/src/services/package-seeder.js +30 -0
- package/dist/src/services/package-seeder.js.map +1 -0
- package/dist/src/services/presence.d.ts +3 -0
- package/dist/src/services/presence.d.ts.map +1 -1
- package/dist/src/services/presence.js +10 -0
- package/dist/src/services/presence.js.map +1 -1
- package/dist/src/services/prompt-defaults.d.ts.map +1 -1
- package/dist/src/services/prompt-defaults.js +8 -3
- package/dist/src/services/prompt-defaults.js.map +1 -1
- package/dist/src/services/scheduler.d.ts +41 -2
- package/dist/src/services/scheduler.d.ts.map +1 -1
- package/dist/src/services/scheduler.js +429 -26
- package/dist/src/services/scheduler.js.map +1 -1
- package/dist/src/services/secretary-authoring.d.ts +71 -0
- package/dist/src/services/secretary-authoring.d.ts.map +1 -0
- package/dist/src/services/secretary-authoring.js +336 -0
- package/dist/src/services/secretary-authoring.js.map +1 -0
- package/dist/src/services/secretary-clarify.d.ts +57 -0
- package/dist/src/services/secretary-clarify.d.ts.map +1 -0
- package/dist/src/services/secretary-clarify.js +104 -0
- package/dist/src/services/secretary-clarify.js.map +1 -0
- package/dist/src/services/secretary-feed.d.ts +11 -0
- package/dist/src/services/secretary-feed.d.ts.map +1 -0
- package/dist/src/services/secretary-feed.js +28 -0
- package/dist/src/services/secretary-feed.js.map +1 -0
- package/dist/src/services/secretary-tick.d.ts +243 -3
- package/dist/src/services/secretary-tick.d.ts.map +1 -1
- package/dist/src/services/secretary-tick.js +273 -3
- package/dist/src/services/secretary-tick.js.map +1 -1
- package/dist/src/services/specialist-runner.d.ts +36 -0
- package/dist/src/services/specialist-runner.d.ts.map +1 -0
- package/dist/src/services/specialist-runner.js +91 -0
- package/dist/src/services/specialist-runner.js.map +1 -0
- package/dist/src/services/specialist.d.ts +9 -0
- package/dist/src/services/specialist.d.ts.map +1 -1
- package/dist/src/services/specialist.js +42 -0
- package/dist/src/services/specialist.js.map +1 -1
- package/dist/src/services/structure-overview.d.ts +13 -0
- package/dist/src/services/structure-overview.d.ts.map +1 -1
- package/dist/src/services/structure-overview.js +34 -11
- package/dist/src/services/structure-overview.js.map +1 -1
- package/dist/src/services/system-agent-responder.d.ts +36 -0
- package/dist/src/services/system-agent-responder.d.ts.map +1 -0
- package/dist/src/services/system-agent-responder.js +107 -0
- package/dist/src/services/system-agent-responder.js.map +1 -0
- package/dist/src/services/system-agent.d.ts +34 -1
- package/dist/src/services/system-agent.d.ts.map +1 -1
- package/dist/src/services/system-agent.js +73 -0
- package/dist/src/services/system-agent.js.map +1 -1
- package/dist/src/storage/interface.d.ts +33 -0
- 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 +37 -0
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +132 -12
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +23 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +60 -8
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/memory.d.ts +29 -1
- package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/memory.js +109 -10
- package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +64 -9
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/app.repository.d.ts +15 -0
- package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/memory.repository.d.ts +41 -1
- package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/organism.repository.d.ts +2 -1
- package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
- package/dist/static/app-catalog.html +469 -122
- package/package.json +2 -1
- package/prisma/schema.postgres.prisma +10 -0
- package/prisma/schema.prisma +12 -0
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
import { api, apiGet, apiPost, apiDelete } from '/js/api.js';
|
|
12
12
|
|
|
13
13
|
/** List all memory entries. Returns array. Optional agentGaii to view another agent's memory. */
|
|
14
|
-
export async function listMemories(agentGaii) {
|
|
15
|
-
const
|
|
14
|
+
export async function listMemories(agentGaii, opts) {
|
|
15
|
+
const p = new URLSearchParams();
|
|
16
|
+
if (agentGaii) p.set('agent', agentGaii);
|
|
17
|
+
if (opts?.archived === 'only') p.set('archived', 'only');
|
|
18
|
+
else if (opts?.archived === 'include') p.set('include_archived', 'true');
|
|
19
|
+
const url = '/v1/memory' + (p.toString() ? '?' + p.toString() : '');
|
|
16
20
|
const data = await apiGet(url);
|
|
17
21
|
const list = data?.data?.items || data?.data?.entries || [];
|
|
18
22
|
return Array.isArray(list) ? list : [];
|
|
@@ -23,9 +27,11 @@ export async function listMemories(agentGaii) {
|
|
|
23
27
|
* (include=meta). Fast for thousands of keys; values are fetched per-key on expand via getMemory().
|
|
24
28
|
* Returns { items, quota } so the tab can render the storage-usage bar.
|
|
25
29
|
*/
|
|
26
|
-
export async function listMemoriesMeta(agentGaii) {
|
|
30
|
+
export async function listMemoriesMeta(agentGaii, opts) {
|
|
27
31
|
let url = '/v1/memory?include=meta';
|
|
28
32
|
if (agentGaii) url += `&agent=${encodeURIComponent(agentGaii)}`;
|
|
33
|
+
if (opts?.archived === 'only') url += '&archived=only';
|
|
34
|
+
else if (opts?.archived === 'include') url += '&include_archived=true';
|
|
29
35
|
const data = await apiGet(url);
|
|
30
36
|
const list = data?.data?.items || data?.data?.entries || [];
|
|
31
37
|
return { items: Array.isArray(list) ? list : [], quota: data?.data?.quota || null };
|
|
@@ -95,7 +95,7 @@ export async function rankOffersByNeed(needText, items) {
|
|
|
95
95
|
const prompt = `NEED: ${needText}\n\nCATALOGUE (${catalogue.length} offers):\n${JSON.stringify(catalogue)}`;
|
|
96
96
|
const r = await api('/v1/ai/complete', {
|
|
97
97
|
method: 'POST',
|
|
98
|
-
body: JSON.stringify({ prompt, systemPrompt, modelRole: 'execution',
|
|
98
|
+
body: JSON.stringify({ prompt, systemPrompt, modelRole: 'execution', app_id: 'offers-need-router' }),
|
|
99
99
|
timeoutMs: 120_000, retries: 0,
|
|
100
100
|
});
|
|
101
101
|
if (!r?.ok) { const e = new Error(r?.error?.message || 'AI call failed'); e.code = r?.error?.code || 'UNKNOWN'; throw e; }
|
|
@@ -390,7 +390,6 @@ export async function generateRaw(description, currentManifest) {
|
|
|
390
390
|
prompt: frameRequest(description, currentManifest),
|
|
391
391
|
systemPrompt: await generatorSystemPrompt(),
|
|
392
392
|
modelRole: 'execution',
|
|
393
|
-
max_tokens: 3000,
|
|
394
393
|
app_id: 'organism-workspace',
|
|
395
394
|
}),
|
|
396
395
|
timeoutMs: 600_000,
|
|
@@ -400,6 +399,16 @@ export async function generateRaw(description, currentManifest) {
|
|
|
400
399
|
return String(resp?.data?.content || '');
|
|
401
400
|
}
|
|
402
401
|
|
|
402
|
+
/** Archive / unarchive organism content (creator/admin). `target` = { level, ws?, namespace?, key? }.
|
|
403
|
+
* Archived content is read-only and hidden from AI materials (overview/read/search) but stays in the
|
|
404
|
+
* archive search; archiving a container cascades, unarchiving uses smart restore. */
|
|
405
|
+
export async function archiveContent(orgId, target) {
|
|
406
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/archive`, target);
|
|
407
|
+
}
|
|
408
|
+
export async function unarchiveContent(orgId, target) {
|
|
409
|
+
return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/unarchive`, target);
|
|
410
|
+
}
|
|
411
|
+
|
|
403
412
|
/** Delete a workspace entirely — removes all organism.{id}.* memory + its schema locks. The
|
|
404
413
|
* organism stays (returns to "no workspace yet"). Deliberate typed-confirmation lives in the UI. */
|
|
405
414
|
export async function deleteWorkspace(orgId, wsId) {
|
|
@@ -458,23 +467,30 @@ ${jsonText}`;
|
|
|
458
467
|
/** Key root for one workspace — an organism holds many workspaces under organism.{id}.w.{wsId}. */
|
|
459
468
|
export function wsRoot(orgId, wsId) { return `organism.${orgId}.w.${wsId}`; }
|
|
460
469
|
|
|
461
|
-
export async function applyGeneratedWorkspace(orgId, wsId, generated) {
|
|
470
|
+
export async function applyGeneratedWorkspace(orgId, wsId, generated, opts = {}) {
|
|
462
471
|
const root = wsRoot(orgId, wsId);
|
|
463
472
|
for (const [namespace, schema] of Object.entries(generated.schemas || {})) {
|
|
464
|
-
|
|
473
|
+
// 'open' (not 'strict'): generated schemas enforce required fields + types but TOLERATE extra fields,
|
|
474
|
+
// so later autonomous/agent writes aren't silently rejected over a stray property. The generator
|
|
475
|
+
// pre-locks structure before any real content exists; strict betoni fights every later write.
|
|
476
|
+
await apiPut(`/v1/memory/${encodeURIComponent(`${root}.${namespace}`)}/schema`, { schema, apply_to: 'prefix', schema_mode: 'open' });
|
|
465
477
|
}
|
|
466
478
|
const manifest = { ...generated.manifest, id: orgId, status: generated.manifest.status || 'active' };
|
|
467
479
|
await apiPost('/v1/memory', { key: `${root}.meta.manifest`, value: manifest, visibility: 'private' });
|
|
468
480
|
await apiPost('/v1/memory', { key: `${root}.meta.readme`, value: `# ${manifest.name || 'Workspace'}\n\n${manifest.summary || ''}`, visibility: 'private' });
|
|
469
481
|
// Write any example instances as DRAFTS (clearly not-yet-published samples). Best-effort: a
|
|
470
|
-
// sample that doesn't validate is skipped, not fatal.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
482
|
+
// sample that doesn't validate is skipped, not fatal. `skipExamples` suppresses these entirely —
|
|
483
|
+
// the Secretary setup path fills workspaces with REAL content instead (fake "example-N" records read
|
|
484
|
+
// as disinformation, not examples), so it never wants placeholders.
|
|
485
|
+
if (!opts.skipExamples) {
|
|
486
|
+
let n = 0;
|
|
487
|
+
for (const [namespace, items] of Object.entries(generated.examples || {})) {
|
|
488
|
+
if (!Array.isArray(items)) continue;
|
|
489
|
+
for (const item of items.slice(0, 5)) {
|
|
490
|
+
n++;
|
|
491
|
+
const id = String((item && item.id) || `example-${n}`).replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
492
|
+
await apiPost('/v1/memory', { key: `${root}.${namespace}.${id}.draft`, value: { ...item, id }, visibility: 'private' }).catch(() => {});
|
|
493
|
+
}
|
|
478
494
|
}
|
|
479
495
|
}
|
|
480
496
|
}
|
|
@@ -483,7 +499,10 @@ export async function applyGeneratedWorkspace(orgId, wsId, generated) {
|
|
|
483
499
|
export async function applyProjectTemplate(orgId, wsId, name, summary) {
|
|
484
500
|
const root = wsRoot(orgId, wsId);
|
|
485
501
|
for (const [namespace, schema] of Object.entries(PROJECT_SCHEMAS)) {
|
|
486
|
-
|
|
502
|
+
// 'open' (not 'strict'): generated schemas enforce required fields + types but TOLERATE extra fields,
|
|
503
|
+
// so later autonomous/agent writes aren't silently rejected over a stray property. The generator
|
|
504
|
+
// pre-locks structure before any real content exists; strict betoni fights every later write.
|
|
505
|
+
await apiPut(`/v1/memory/${encodeURIComponent(`${root}.${namespace}`)}/schema`, { schema, apply_to: 'prefix', schema_mode: 'open' });
|
|
487
506
|
}
|
|
488
507
|
await apiPost('/v1/memory', { key: `${root}.meta.manifest`, value: projectManifest(orgId, name, summary), visibility: 'private' });
|
|
489
508
|
await apiPost('/v1/memory', { key: `${root}.meta.readme`, value: `# ${name}\n\n${summary || ''}`, visibility: 'private' });
|
|
@@ -533,8 +552,11 @@ export async function getWorkspaceActivity(orgId, wsId) {
|
|
|
533
552
|
}
|
|
534
553
|
|
|
535
554
|
/** Read the manifest-driven workspace. Returns null if the workspace has no manifest yet. */
|
|
536
|
-
export async function getWorkspace(orgId, wsId) {
|
|
537
|
-
|
|
555
|
+
export async function getWorkspace(orgId, wsId, opts) {
|
|
556
|
+
let url = `/v1/organisms/${encodeURIComponent(orgId)}/workspace?ws=${encodeURIComponent(wsId)}`;
|
|
557
|
+
if (opts?.archived === 'only') url += '&archived=only';
|
|
558
|
+
else if (opts?.includeArchived) url += '&includeArchived=true';
|
|
559
|
+
const resp = await apiGet(url);
|
|
538
560
|
return resp?.data || null;
|
|
539
561
|
}
|
|
540
562
|
|
|
@@ -1058,7 +1080,7 @@ export async function addSpace(orgId, wsId, manifest, name, mode) {
|
|
|
1058
1080
|
if (mode === 'records') {
|
|
1059
1081
|
await apiPut(`/v1/memory/${encodeURIComponent(`${wsRoot(orgId, wsId)}.${namespace}`)}/schema`, {
|
|
1060
1082
|
schema: { type: 'object', required: ['id', 'title'], properties: { id: { type: 'string' }, title: { type: 'string' } } },
|
|
1061
|
-
apply_to: 'prefix', schema_mode: '
|
|
1083
|
+
apply_to: 'prefix', schema_mode: 'open', // tolerate extra fields; enforce required/types only (see applyGeneratedWorkspace)
|
|
1062
1084
|
});
|
|
1063
1085
|
}
|
|
1064
1086
|
return saveManifest(orgId, wsId, { ...manifest, objectTypes: [...(manifest.objectTypes || []), ot] });
|
|
@@ -1206,9 +1228,19 @@ export async function setImageVisibility(key, visibility) {
|
|
|
1206
1228
|
//  — public, anyone loads it via a plain <img>
|
|
1207
1229
|
const STORAGE_IMG_RE = /!\[([^\]]*)\]\(\/v1\/(?:storage|pub\/[^/)]+)\/([^\s)]+)\)/g;
|
|
1208
1230
|
|
|
1209
|
-
/** The current owner's GHII (`owner@node`)
|
|
1231
|
+
/** The current owner's GHII (`owner@node`). ALWAYS returns a string so callers can safely `.split('@')`.
|
|
1232
|
+
* Prefers the synchronous `auth.storedGhii` accessor; falls back to a sync `getSession()` only when it
|
|
1233
|
+
* returns a session object with a string `ghii` (on some builds `getSession()` is async / returns a
|
|
1234
|
+
* Promise — never return that, or `.split` blows up at call sites). Empty string when unknown. */
|
|
1210
1235
|
export function currentGhii() {
|
|
1211
|
-
|
|
1236
|
+
const auth = (typeof window !== 'undefined' && window.AIMEAT && window.AIMEAT.auth) || null;
|
|
1237
|
+
if (!auth) return '';
|
|
1238
|
+
if (typeof auth.storedGhii === 'string' && auth.storedGhii) return auth.storedGhii;
|
|
1239
|
+
try {
|
|
1240
|
+
const s = typeof auth.getSession === 'function' ? auth.getSession() : null;
|
|
1241
|
+
if (s && typeof s === 'object' && typeof s.ghii === 'string') return s.ghii;
|
|
1242
|
+
} catch { /* ignore */ }
|
|
1243
|
+
return '';
|
|
1212
1244
|
}
|
|
1213
1245
|
|
|
1214
1246
|
/** Pull the storage object keys (+ alt text) embedded in a markdown document, in order (both forms). */
|
|
@@ -5,23 +5,64 @@
|
|
|
5
5
|
* from AI text, a brain version-snapshot, a context id generator, and the secretary.config migration
|
|
6
6
|
* from the old single-context layout to the multi-context `{contexts[], activeContextId}` shape.
|
|
7
7
|
* See docs/plans/2026-06-23-secretary-feature.md (§5 hire, §22 multi-context).
|
|
8
|
-
* @structure CONTRACT · buildInterviewPrompt · buildDesignPrompt · extractJson · snapshotOf · genCtxId · migrateConfig · buildDecisionRecord
|
|
9
|
-
* @usage import { buildInterviewPrompt, extractJson, migrateConfig } from '/js/services/secretary-helpers.js';
|
|
8
|
+
* @structure CONTRACT · buildInterviewPrompt · buildDesignPrompt · extractJson · snapshotOf · genCtxId · sanitizeQuickActions · migrateConfig · buildDecisionRecord
|
|
9
|
+
* @usage import { buildInterviewPrompt, extractJson, sanitizeQuickActions, migrateConfig } from '/js/services/secretary-helpers.js';
|
|
10
10
|
* @version-history
|
|
11
|
+
* v0.6.0 — 2026-06-30 — CONTRACT steers the design toward DOCUMENT spaces (free-form markdown, the
|
|
12
|
+
* default) and treats RECORDS spaces as the exception (only genuine lists/task-sets) — workspaces
|
|
13
|
+
* were coming out mostly as rigid record spaces.
|
|
14
|
+
* v0.5.0 — 2026-06-30 — Close the milestone↔goal linking gap: CONTRACT milestones now carry "goalRefs"
|
|
15
|
+
* (the AI links each gate to the goals it gates, by 1-based goal index) + linkMilestoneGoals() to
|
|
16
|
+
* resolve those indices to seeded goal ids, keepKnownGoalRefs() to validate re-plan id refs, and
|
|
17
|
+
* buildStrategyReplanPrompt() now lists existing goals so re-plans preserve/relink them. Removes the
|
|
18
|
+
* manual "tick the linked goals" step the owner used to face after setup.
|
|
19
|
+
* v0.4.0 — 2026-06-28 — Setup interview reworked: deeper questions (current state, target, ordered
|
|
20
|
+
* milestones, who's involved), a mandatory reflect-back-and-confirm step before any JSON, and a
|
|
21
|
+
* closing "paste it back into AIMEAT" instruction. CONTRACT gains an OPTIONAL `direction` block
|
|
22
|
+
* (where-we-are → where-we're-going + milestones) + normalizeDirection().
|
|
23
|
+
* v0.3.0 — 2026-06-27 — B3: CONTRACT gains quickActions (2–3 role-specific shortcuts) + sanitizeQuickActions
|
|
24
|
+
* — the frontend security mirror (brain/secretary may only seed prompt|compose, never a run verb).
|
|
11
25
|
* v0.2.0 — 2026-06-24 — P3-B: buildDecisionRecord — the single source of the decision-log contract
|
|
12
26
|
* shape, shared by the manual log form (use-learning), answered Ask cards (use-intake), and
|
|
13
27
|
* approved guided plans (use-guided-plan) so real choices feed the learning loop.
|
|
14
28
|
* v0.1.0 — 2026-06-23 — Extracted from views/secretary.js (Phase 2, keep view < 500 lines).
|
|
15
29
|
*/
|
|
16
30
|
import { defaultPolicy } from '/js/services/secretary-policy.js';
|
|
31
|
+
import { sanitizeProposedQuickActions } from '/js/services/secretary-rules.js';
|
|
32
|
+
import { t } from '/js/i18n.js';
|
|
33
|
+
|
|
34
|
+
/** G10: a human day label for a feed timestamp — Today / Yesterday / locale date. */
|
|
35
|
+
export function feedDayLabel(ts) {
|
|
36
|
+
const d = new Date(ts);
|
|
37
|
+
const startOf = (x) => { const y = new Date(x); y.setHours(0, 0, 0, 0); return y.getTime(); };
|
|
38
|
+
const diffDays = Math.round((startOf(new Date()) - startOf(d)) / 86400000);
|
|
39
|
+
if (diffDays === 0) return t('secretary.feed.today');
|
|
40
|
+
if (diffDays === 1) return t('secretary.feed.yesterday');
|
|
41
|
+
return d.toLocaleDateString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** G10: group a newest-first feed list into consecutive day buckets [{ key, label, items }]. The key is
|
|
45
|
+
* the LOCAL midnight timestamp (NOT the UTC date) so it matches the local-day label — otherwise items
|
|
46
|
+
* straddling UTC midnight on the same local day would split into two same-labelled groups. */
|
|
47
|
+
export function groupFeedByDay(feed) {
|
|
48
|
+
const groups = [];
|
|
49
|
+
let cur = null;
|
|
50
|
+
for (const it of (feed || [])) {
|
|
51
|
+
let key = 'unknown';
|
|
52
|
+
if (it.ts) { const d = new Date(it.ts); d.setHours(0, 0, 0, 0); key = String(d.getTime()); }
|
|
53
|
+
if (!cur || cur.key !== key) { cur = { key, label: it.ts ? feedDayLabel(it.ts) : '—', items: [] }; groups.push(cur); }
|
|
54
|
+
cur.items.push(it);
|
|
55
|
+
}
|
|
56
|
+
return groups;
|
|
57
|
+
}
|
|
17
58
|
|
|
18
59
|
/** Concise, accurate AIMEAT primer fed to the chat so the Secretary can teach/guide the user
|
|
19
60
|
* ("tutustuta AIMEATiin"). Concept-level (avoids brittle exact UI paths so it doesn't drift). */
|
|
20
61
|
export const SECRETARY_AIMEAT_PRIMER = `AIMEAT is the user's own AI-agent platform. Key ideas you can explain:
|
|
21
62
|
- The user owns everything under their identity (GHII). "Morsels" (❤️) are the usage/credit meter — they're spent on actions, not cashable.
|
|
22
63
|
- Memory: the user's private key-value knowledge store.
|
|
23
|
-
- Organisms = spaces (personal or shared) that contain Workspaces (like folders) holding structured records.
|
|
24
|
-
- Agents: AI helpers the user connects (each scoped);
|
|
64
|
+
- Organisms = spaces (personal or shared) that contain Workspaces (like folders) holding structured records. You keep each area of the user's stuff in its own organism.
|
|
65
|
+
- Agents: AI helpers the user connects (each scoped); you are one of them.
|
|
25
66
|
- Discover (the network search): finds what already exists across the node — agents, services, companies' offerings, knowledge, apps — so you never rebuild what's there. Always suggest discovering before building.
|
|
26
67
|
- Apps, extensions, capabilities: ways to extend AIMEAT; companies can publish offerings others order.
|
|
27
68
|
When the user asks how to do something in AIMEAT, explain clearly and concretely from these concepts, point them to the right area of their Profile, and offer to walk them through it step by step. If unsure, say so rather than inventing exact button names.`;
|
|
@@ -30,42 +71,104 @@ When the user asks how to do something in AIMEAT, explain clearly and concretely
|
|
|
30
71
|
export const CONTRACT = `\`\`\`json
|
|
31
72
|
{
|
|
32
73
|
"brain": {
|
|
33
|
-
"purpose": "1-3 sentences: who
|
|
74
|
+
"purpose": "1-3 sentences: who you are for me and how you help me in this context",
|
|
34
75
|
"rules": [ { "id": "r1", "description": "a concrete operating rule" } ]
|
|
35
76
|
},
|
|
36
77
|
"organism": {
|
|
37
78
|
"name": "a short name for this space",
|
|
38
79
|
"description": "one line describing it",
|
|
39
|
-
"workspaces": [ { "name": "workspace name", "purpose": "what it holds and why" } ]
|
|
80
|
+
"workspaces": [ { "name": "workspace name", "purpose": "what it holds and why", "manifest": "OPTIONAL — see note below", "schemas": "OPTIONAL — see note below" } ]
|
|
81
|
+
},
|
|
82
|
+
"goals": [ { "title": "a concrete goal I'm working toward in this context", "why": "why it matters" } ],
|
|
83
|
+
"quickActions": [ { "label": "short button label (<= 4 words)", "kind": "prompt", "prompt": "a ready message to send you" } ],
|
|
84
|
+
"strategy": "OPTIONAL — see note below"
|
|
85
|
+
}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
A workspace's "manifest.objectTypes" are its SPACES, and each space is one of two kinds. PREFER DOCUMENT spaces:
|
|
89
|
+
- A DOCUMENT space — free-form markdown pages about a topic (guides, write-ups, notes, plans, analysis, anything). Declare it as { "name": "<type>", "namespace": "<ns>", "mode": "document" } with NO fields and NO schema. This is the DEFAULT choice: it holds any kind of information and can be shaped freely, so most spaces should be document spaces.
|
|
90
|
+
- A RECORDS space — a schema-locked list where every entry shares the SAME fixed fields. Use this ONLY when the content is genuinely a list or task-like set (a pipeline, a task list, a tracker). Declare it as { "name": "<type>", "namespace": "<ns>", "mode": "records", "fields": [ { "name": "<field>", "type": "text|number|date|select|boolean|reference" } ] } AND provide a matching JSON Schema under "schemas" keyed by its namespace.
|
|
91
|
+
Lean toward document spaces; reach for a records space only when fixed, repeating fields clearly fit. Full manifest shape: { "manifestVersion": "1.0", "name": "<the workspace name>", "kind": "<short-slug>", "status": "active", "objectTypes": [ …the spaces above ] }.
|
|
92
|
+
If you are NOT confident you can produce a VALID manifest + schemas, OMIT both fields for that workspace — they will be designed automatically afterward. Never output an invalid or half-finished manifest.
|
|
93
|
+
|
|
94
|
+
"strategy" is the OPTIONAL steering frame for this area: where we are, where we're going, and how. Include it ONLY when the user wants this area steered that way (many goal-driven areas benefit; some areas are just ongoing upkeep — then OMIT the field entirely). EVERY sub-field is itself optional — include only the ones the user gave you real material for; never invent vision/mission/progress/names. When included:
|
|
95
|
+
- "strategy": {
|
|
96
|
+
"enabled": true,
|
|
97
|
+
"vision": "the long-term aspirational picture — where this could ultimately go (optional)",
|
|
98
|
+
"mission": "what we do / our ongoing purpose in this area (optional)",
|
|
99
|
+
"principles": [ "a short guardrail that should steer decisions" ],
|
|
100
|
+
"risks": [ "something that could derail this, to watch for" ],
|
|
101
|
+
"current": "where this area honestly stands right now (the current state)",
|
|
102
|
+
"target": "the target state — what 'done well' looks like for this strategy cycle (no dates)",
|
|
103
|
+
"milestones": [ { "title": "an ordered gate on the way to the target", "enables": "what reaching it unlocks / why it matters", "criterion": "an external/judgement condition to consider it reached (optional)", "goalRefs": [1, 2], "status": "not-started" } ]
|
|
40
104
|
}
|
|
105
|
+
- Milestones are ORDERED and DATELESS; status is one of "not-started" | "in-progress" | "reached". A milestone is a checkpoint (a gate), NOT a task — the concrete tasks live in "goals". Base everything ONLY on what the user told you.
|
|
106
|
+
- "goalRefs" links each milestone to the goals it gates: list the 1-based positions of the goals (in the "goals" array above) whose completion moves this milestone toward "reached". Every goal should serve at least one milestone, and most milestones gate at least one goal — leave "goalRefs": [] only when a milestone genuinely maps to none. This linkage is what lets a milestone advance automatically as its goals get done, so fill it in — do not leave it empty by default.`;
|
|
107
|
+
|
|
108
|
+
/** When EVOLVING an existing context (re-run "reshape current"), the current setup fed back so the AI
|
|
109
|
+
* modifies it instead of starting over — and keeps the existing workspaces stable. Empty for a new context. */
|
|
110
|
+
function currentSetupText(current) {
|
|
111
|
+
if (!current) return '';
|
|
112
|
+
const rules = ((current.brain && current.brain.rules) || []).map((r) => `- ${r.description}`).join('\n');
|
|
113
|
+
const ws = (current.workspaces || []).map((w) => `- ${w.name}: ${w.purpose || ''}`).join('\n');
|
|
114
|
+
return `
|
|
115
|
+
|
|
116
|
+
This area already exists, so build on what is here and evolve it.
|
|
117
|
+
Current purpose: ${(current.brain && current.brain.purpose) || ''}
|
|
118
|
+
Current rules:
|
|
119
|
+
${rules || '(none)'}
|
|
120
|
+
Current workspaces:
|
|
121
|
+
${ws || '(none)'}
|
|
122
|
+
Keep the existing workspaces under their current names, add a new one only when the change genuinely calls for it, and update the brain, goals and direction to reflect what changes below.`;
|
|
41
123
|
}
|
|
42
|
-
\`\`\``;
|
|
43
124
|
|
|
44
|
-
/** Conversational interview the user runs in their own external AI chat.
|
|
45
|
-
|
|
46
|
-
|
|
125
|
+
/** Conversational interview the user runs in their own external AI chat. `current` (optional) = the
|
|
126
|
+
* context being re-run, so the AI EVOLVES it (reshape) instead of starting fresh. */
|
|
127
|
+
export function buildInterviewPrompt(owner, current) {
|
|
128
|
+
return `I want to ${current ? 'evolve the' : 'set up an'} AI that works alongside me inside AIMEAT and helps me handle everything I have going on across my work and life${owner ? ` (my username is "${owner}")` : ''}.
|
|
129
|
+
|
|
130
|
+
You are that, and this is the start of us working together. You take things off my plate and move my work forward, and you become whatever I need you to be — there is no fixed job title here, only what helps me most. Treat it as a real, flowing conversation between two people. Your aim: genuinely understand my world and what I have going on, be useful to me from the very first message, and by the end arrive at genuinely good "brains" for yourself, so you clearly know how to help me across all of it, my goals are sharp, and you have spaces ready to fill with well-organized information as we go. You handle the whole picture and organize what I bring into its own spaces yourself, so I can hand you anything and trust it lands in the right place.
|
|
131
|
+
|
|
132
|
+
Take whatever I bring you in stride: it is all welcome, however much there is, and a big, busy picture is exactly where you are most useful. Stay calm and can-do, and meet a large scope with energy. Keep any concern for the moments when something is genuinely serious and worth my attention; right now we are getting to know things, so stay light, curious, and encouraging.
|
|
47
133
|
|
|
48
|
-
|
|
134
|
+
Begin by grounding yourself in what already exists. Your very first move, while you have AIMEAT tools available, is to look up what I already have — my organisms, their workspaces, and recent activity — and open the conversation from that real picture, naming only what you actually find there and building on it. When AIMEAT tools are out of reach, open instead by asking me for a quick picture of what I have going on. Either way, start from what is really there.
|
|
49
135
|
|
|
50
|
-
|
|
136
|
+
Lead with the conversation and let it lead everywhere. Talk the way a sharp, warm colleague talks: plain, simple, everyday language, in my own language. Stay curious and genuinely helpful: when I bring up an approach, a tool, or a question, take it seriously, build on it, and help me with it right there; and when you can move something forward in the moment (look something up, draft something, check whether something already exists), do it then. You have AIMEAT tools and knowledge available, so use them to help me and to ask sharper questions.
|
|
137
|
+
|
|
138
|
+
You know AIMEAT, so you can explain it and suggest how it helps here:
|
|
139
|
+
${SECRETARY_AIMEAT_PRIMER}
|
|
140
|
+
|
|
141
|
+
Follow me, and come to understand, in whatever order it surfaces: what I have going on across my work and life and where things honestly stand right now; what I need you for and how you can best help me; and, as it comes up, where I want to take things and how we get there. Keep your questions few and focused, one small batch at a time, and let my answers shape the next ones. Where you have a hunch, from what you know about me or what you can find, offer it as a suggestion I can shape ("maybe you're aiming at X, or is it something else?") and let me steer.
|
|
142
|
+
|
|
143
|
+
Keep your words to me human and plain, and work out any underlying structure quietly on your own from what I tell you, holding it in the background.${currentSetupText(current)}
|
|
144
|
+
|
|
145
|
+
As we talk, work toward one clear picture we both share, and say it back to me in plain words (your own, grounded in what we covered and what you looked up — a few sentences, not a labelled form): where things honestly stand now, where this is ultimately heading (the vision), and what success looks like for the near term (the target). Make that picture hang together as one coherent story, so each piece follows from the one before. Anything you then suggest doing is a step that plainly serves that target and connects to the rest, expressed as a real, concrete outcome a sharp colleague would immediately understand. Say everything plainly enough that I nod and recognize it as true; when a phrase would puzzle me, swap it for the plain thing you mean, and keep invented terms and strategy-speak out of it.
|
|
146
|
+
|
|
147
|
+
Hold the right altitude, and use your judgment to keep it there. The vision, target and milestones describe real outcomes — where this area is genuinely heading and what reaching it changes, in this area's own terms, whatever they are. The concrete to-dos (prepare a thing, send a message, make a list) are everyday work that belongs in the space, never a goal or a milestone. Before you settle on the picture, step back and judge it the way a thoughtful person would: is this the real shape of where we are going, or a list of tasks dressed up as strategy? If it reads as busywork, or it does not hang together sensibly, rework it until it earns its place.
|
|
148
|
+
|
|
149
|
+
Once that picture feels right to me — I will tell you when it clicks — capture everything we landed on as ONE JSON object inside a single code block in exactly this shape:
|
|
51
150
|
|
|
52
151
|
${CONTRACT}
|
|
53
152
|
|
|
54
|
-
|
|
153
|
+
Fill it from the picture we confirmed: the brain (who you are for me and how you help, written as clear operating rules), the space and what it will hold so information stays organized as it accumulates, the direction (the vision, where we are now, and the target we agreed), my goals (each one a concrete outcome that plainly serves that target and connects to the rest — never a list of disconnected tasks), and a couple of quick actions. Keep it to 3–7 brain rules, 2–6 workspaces drawn from my actual needs, 2–3 goals, and 2–3 quickActions, each kind being either "prompt" (a ready message to send me) or "compose" (focus an input, with "target" one of "plan"|"find"|"note"). Keep the JSON as one self-contained code block so the app can read it.
|
|
154
|
+
|
|
155
|
+
After the JSON, add one short line in my language: go back to AIMEAT → the Secretary page → paste this into the setup box → "Set up my Secretary".`;
|
|
55
156
|
}
|
|
56
157
|
|
|
57
|
-
/** Single-shot design prompt for the in-app mode (runs on the owner's OpenRouter key).
|
|
58
|
-
|
|
59
|
-
|
|
158
|
+
/** Single-shot design prompt for the in-app mode (runs on the owner's OpenRouter key). `current`
|
|
159
|
+
* (optional) = the context being re-run, so the AI EVOLVES it (reshape) and keeps its workspaces. */
|
|
160
|
+
export function buildDesignPrompt(owner, needs, current) {
|
|
161
|
+
return `You are setting up ${current ? 'and evolving ' : ''}one context of the AI that works alongside ${owner || 'a user'} inside AIMEAT (it takes things off their plate and moves their work forward — no fixed job title, only what helps most). Based ONLY on the needs described below${current ? ' and the current setup' : ''}, design these things and output them as ONE JSON object inside a single code block with EXACTLY this shape and nothing else:
|
|
60
162
|
|
|
61
163
|
${CONTRACT}
|
|
164
|
+
${currentSetupText(current)}
|
|
62
165
|
|
|
63
166
|
The user's needs:
|
|
64
167
|
"""
|
|
65
168
|
${needs}
|
|
66
169
|
"""
|
|
67
170
|
|
|
68
|
-
Constraints: 3–7 brain rules; 2–6 workspaces (you choose names + purposes from the needs).
|
|
171
|
+
Constraints: 3–7 brain rules; 2–6 workspaces (you choose names + purposes from the needs); 2–3 goals (concrete things the user is working toward); 2–3 quickActions (role-specific shortcut buttons), each "kind" being either "prompt" (a ready message) or "compose" (focus an input, with "target" one of "plan"|"find"|"note"). Include the "strategy" object (vision/mission/principles/risks/current → target → ordered milestones, using the parts the needs support) when the needs describe a goal-driven area heading somewhere, and leave it out for areas that are ongoing upkeep. Hold the right altitude: the strategy and goals are real outcomes — where this area is heading and what reaching it changes, in its own terms — while concrete to-dos (prepare a thing, send a message, make a list) belong in a workspace, never as a goal or milestone. Before you output, judge the result the way a thoughtful person would: if a goal or milestone reads as busywork or a task dressed up as strategy, rework it until it is a real outcome that hangs together. Ground everything in the needs as given. Output the single JSON code block on its own.`;
|
|
69
172
|
}
|
|
70
173
|
|
|
71
174
|
/** Pull a JSON object out of an AI's text (may be fenced / surrounded by prose). */
|
|
@@ -92,6 +195,18 @@ export function genCtxId() {
|
|
|
92
195
|
return 'ctx-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
|
93
196
|
}
|
|
94
197
|
|
|
198
|
+
function genActionId() { return 'qa-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Sanitize brain-seeded / secretary-proposed dynamic quick actions (B3). The SECURITY boundary (only
|
|
202
|
+
* prompt|compose, never a 'run' verb) lives in the shared, dependency-free rules module
|
|
203
|
+
* (/js/services/secretary-rules.js), which MIRRORS the server's secretary-tick.ts and is proven equal by
|
|
204
|
+
* e2e-secretary's G6 parity test. Here we just stamp an id + createdAt onto each sanitized action.
|
|
205
|
+
*/
|
|
206
|
+
export function sanitizeQuickActions(raw, source, status = 'proposed') {
|
|
207
|
+
return sanitizeProposedQuickActions(raw, source, status).map((a) => ({ id: genActionId(), ...a, createdAt: new Date().toISOString() }));
|
|
208
|
+
}
|
|
209
|
+
|
|
95
210
|
/** Cheap, AI-free context routing (plan §22 step 2): score `text` against each context's name +
|
|
96
211
|
* purpose + workspaces by word overlap. Returns {id,name} of a NON-active context only when it
|
|
97
212
|
* clearly beats the active one — so the view can SUGGEST a switch. Null otherwise (no nagging). */
|
|
@@ -165,6 +280,136 @@ export function migrateConfig(cfg, directives) {
|
|
|
165
280
|
return { config: { contexts: [], activeContextId: null }, changed: false };
|
|
166
281
|
}
|
|
167
282
|
|
|
283
|
+
/** Valid milestone statuses for the optional "strategy" steering structure. */
|
|
284
|
+
export const MILESTONE_STATUSES = ['not-started', 'in-progress', 'reached'];
|
|
285
|
+
|
|
286
|
+
let _msSeq = 0;
|
|
287
|
+
/** Stable-ish id for a milestone (needed so goals can reference it + for list keys). */
|
|
288
|
+
function genMilestoneId() { return 'ms-' + Date.now().toString(36) + (_msSeq++).toString(36) + Math.random().toString(36).slice(2, 4); }
|
|
289
|
+
|
|
290
|
+
/** Coerce a free-form list field (principles/risks) to an array of non-empty short strings. */
|
|
291
|
+
function strList(v) {
|
|
292
|
+
const arr = Array.isArray(v) ? v : (typeof v === 'string' && v.trim() ? [v] : []);
|
|
293
|
+
return arr.map((x) => String((x && x.text) || x || '').trim().slice(0, 400)).filter(Boolean).slice(0, 20);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Normalize a context's optional `strategy` (from interview/design JSON or stored config) into the
|
|
297
|
+
* canonical shape: `{ enabled, vision, mission, principles[], risks[], current, target, milestones[] }`
|
|
298
|
+
* where each milestone is `{ id, title, enables, goalRefs[], criterion, status }`. A missing object, a
|
|
299
|
+
* non-object, or `enabled:false` yields a disabled (hidden) strategy. Every sub-field is optional; the
|
|
300
|
+
* card shows only the filled ones. Milestones keep their order (a gate; reach one before the next),
|
|
301
|
+
* empty-title ones are dropped, ids are stamped if absent, and statuses are coerced to a valid value. */
|
|
302
|
+
export function normalizeStrategy(s) {
|
|
303
|
+
const empty = { enabled: false, vision: '', mission: '', principles: [], risks: [], current: '', target: '', milestones: [] };
|
|
304
|
+
if (!s || typeof s !== 'object' || s.enabled === false) return empty;
|
|
305
|
+
const ms = Array.isArray(s.milestones) ? s.milestones : [];
|
|
306
|
+
return {
|
|
307
|
+
enabled: true,
|
|
308
|
+
vision: String(s.vision || '').slice(0, 4000),
|
|
309
|
+
mission: String(s.mission || '').slice(0, 4000),
|
|
310
|
+
principles: strList(s.principles),
|
|
311
|
+
risks: strList(s.risks),
|
|
312
|
+
current: String(s.current || '').slice(0, 4000),
|
|
313
|
+
target: String(s.target || '').slice(0, 4000),
|
|
314
|
+
milestones: ms.map((m) => ({
|
|
315
|
+
id: (m && typeof m.id === 'string' && m.id) ? m.id : genMilestoneId(),
|
|
316
|
+
title: String((m && m.title) || '').slice(0, 300),
|
|
317
|
+
enables: String((m && (m.enables || m.who)) || '').slice(0, 600),
|
|
318
|
+
goalRefs: Array.isArray(m && m.goalRefs) ? m.goalRefs.map((g) => String(g)).filter(Boolean).slice(0, 50) : [],
|
|
319
|
+
criterion: String((m && m.criterion) || '').slice(0, 600),
|
|
320
|
+
status: MILESTONE_STATUSES.includes(m && m.status) ? m.status : 'not-started',
|
|
321
|
+
})).filter((m) => m.title),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Suggested milestone status from its linked goals (Phase-2 helper): all linked goals done → "reached";
|
|
326
|
+
* any linked goal exists and at least one is done/in-flight → "in-progress"; otherwise unchanged. Pure;
|
|
327
|
+
* the owner confirms (some milestones also carry an external `criterion` that is a judgement call). */
|
|
328
|
+
export function suggestMilestoneStatus(milestone, goalsById) {
|
|
329
|
+
const refs = (milestone && milestone.goalRefs) || [];
|
|
330
|
+
if (!refs.length) return milestone.status || 'not-started';
|
|
331
|
+
const linked = refs.map((id) => goalsById[id]).filter(Boolean);
|
|
332
|
+
if (!linked.length) return milestone.status || 'not-started';
|
|
333
|
+
const done = linked.filter((g) => g.status === 'done').length;
|
|
334
|
+
if (done === linked.length) return 'reached';
|
|
335
|
+
if (done > 0) return 'in-progress';
|
|
336
|
+
return milestone.status || 'not-started';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Resolve milestone goalRefs the setup/design AI produced as 1-based indices into the same JSON's
|
|
340
|
+
* `goals` array (it cannot know the runtime goal ids, so it links by position) to the real goal ids
|
|
341
|
+
* assigned at seed time. `idsInOrder` is the seeded goal ids in goals[] order (null for any goal that
|
|
342
|
+
* was skipped or failed to persist). Refs that don't resolve to a real id are dropped; pass [] to strip
|
|
343
|
+
* every index ref (e.g. when goals were not seeded, so no link can be made). Returns a new strategy;
|
|
344
|
+
* non-strategy input passes through unchanged. Run this AFTER normalizeStrategy. */
|
|
345
|
+
export function linkMilestoneGoals(strategy, idsInOrder) {
|
|
346
|
+
if (!strategy || typeof strategy !== 'object' || !Array.isArray(strategy.milestones)) return strategy;
|
|
347
|
+
const ids = Array.isArray(idsInOrder) ? idsInOrder : [];
|
|
348
|
+
return {
|
|
349
|
+
...strategy,
|
|
350
|
+
milestones: strategy.milestones.map((m) => {
|
|
351
|
+
const refs = Array.isArray(m && m.goalRefs) ? m.goalRefs : [];
|
|
352
|
+
const mapped = refs
|
|
353
|
+
.map((r) => { const n = Number(r); return Number.isInteger(n) && n >= 1 && n <= ids.length ? ids[n - 1] : null; })
|
|
354
|
+
.filter(Boolean);
|
|
355
|
+
return { ...m, goalRefs: mapped };
|
|
356
|
+
}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Keep only the milestone goalRefs that point at a goal that actually exists (matched by id) — used
|
|
361
|
+
* after a re-plan, where the AI links milestones to EXISTING goal ids and could name one that's gone. */
|
|
362
|
+
export function keepKnownGoalRefs(strategy, validIds) {
|
|
363
|
+
if (!strategy || typeof strategy !== 'object' || !Array.isArray(strategy.milestones)) return strategy;
|
|
364
|
+
const ok = new Set((validIds || []).filter(Boolean).map((x) => String(x)));
|
|
365
|
+
return {
|
|
366
|
+
...strategy,
|
|
367
|
+
milestones: strategy.milestones.map((m) => ({
|
|
368
|
+
...m,
|
|
369
|
+
goalRefs: (Array.isArray(m && m.goalRefs) ? m.goalRefs : []).filter((r) => ok.has(String(r))),
|
|
370
|
+
})),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Build a re-plan prompt: given the CURRENT strategy + what the owner just changed (e.g. a new target
|
|
375
|
+
* state), ask the AI to propose an UPDATED strategy that stays coherent — keep what still fits, adjust
|
|
376
|
+
* milestones/principles/risks toward the new target. Returns ONLY a `{ "strategy": {...} }` JSON object,
|
|
377
|
+
* so the same extractJson + normalizeStrategy path applies. Used by both the in-app (OpenRouter) re-plan
|
|
378
|
+
* and the copy-to-chat re-plan. `changeNote` is the human description of what changed. */
|
|
379
|
+
export function buildStrategyReplanPrompt(owner, strategy, changeNote, goals) {
|
|
380
|
+
const cur = normalizeStrategy(strategy);
|
|
381
|
+
const ms = (cur.milestones || []).map((m, i) => {
|
|
382
|
+
const links = (m.goalRefs || []).length ? ` — gates goals: ${m.goalRefs.join(', ')}` : '';
|
|
383
|
+
return `${i + 1}. [${m.status}] ${m.title}${m.enables ? ` — enables: ${m.enables}` : ''}${links}`;
|
|
384
|
+
}).join('\n') || '(none)';
|
|
385
|
+
const goalList = (Array.isArray(goals) ? goals : []).filter((g) => g && g.id && g.title)
|
|
386
|
+
.map((g) => `- [${g.id}] ${g.title}${g.status ? ` (${g.status})` : ''}`).join('\n') || '(none)';
|
|
387
|
+
return `You are helping ${owner || 'a user'} re-plan the strategy for one area of their work inside AIMEAT. The user just changed something and the rest of the strategy should be adjusted to stay coherent — KEEP whatever still fits, and only change what the change implies. Do not invent facts or progress; preserve the status of milestones that are still valid.
|
|
388
|
+
|
|
389
|
+
Current strategy:
|
|
390
|
+
- Vision: ${cur.vision || '(none)'}
|
|
391
|
+
- Mission: ${cur.mission || '(none)'}
|
|
392
|
+
- Principles: ${cur.principles.join(' | ') || '(none)'}
|
|
393
|
+
- Risks: ${cur.risks.join(' | ') || '(none)'}
|
|
394
|
+
- Current state: ${cur.current || '(none)'}
|
|
395
|
+
- Target state: ${cur.target || '(none)'}
|
|
396
|
+
- Milestones (in order):\n${ms}
|
|
397
|
+
|
|
398
|
+
The user's existing goals in this area (link milestones to these by their [id] in "goalRefs"):
|
|
399
|
+
${goalList}
|
|
400
|
+
|
|
401
|
+
What changed:
|
|
402
|
+
"""
|
|
403
|
+
${String(changeNote || '').slice(0, 2000)}
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
Output ONE JSON object inside a single code block and nothing else, with this shape:
|
|
407
|
+
\`\`\`json
|
|
408
|
+
{ "strategy": { "enabled": true, "vision": "", "mission": "", "principles": [], "risks": [], "current": "", "target": "", "milestones": [ { "title": "", "enables": "", "criterion": "", "goalRefs": [], "status": "not-started|in-progress|reached" } ] } }
|
|
409
|
+
\`\`\`
|
|
410
|
+
Keep milestones ORDERED and dateless; carry over the status of milestones that still apply. Set each milestone's "goalRefs" to the [id]s (exact id strings from the goals list above) of the goals it gates — carry over the existing links shown next to each milestone, and [] only when a milestone gates none. Output ONLY the JSON code block.`;
|
|
411
|
+
}
|
|
412
|
+
|
|
168
413
|
/** Path to the decision-log Memory Contract spec (mirrors use-learning.js). */
|
|
169
414
|
export const DECISION_SPEC = 'docs/specs/secretary-decision-contract.md';
|
|
170
415
|
|
|
@@ -31,6 +31,7 @@ export const SECRETARY_CAPABILITIES = [
|
|
|
31
31
|
{ id: 'file_intake', defaultBand: 'act' },
|
|
32
32
|
{ id: 'briefing', defaultBand: 'act' },
|
|
33
33
|
{ id: 'reminders', defaultBand: 'act' },
|
|
34
|
+
{ id: 'author_content', defaultBand: 'off', costs: true },
|
|
34
35
|
{ id: 'curate_knowledge', defaultBand: 'draft' },
|
|
35
36
|
{ id: 'draft_replies', defaultBand: 'draft' },
|
|
36
37
|
{ id: 'create_resource', defaultBand: 'ask' },
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file secretary-rules.js
|
|
3
|
+
* @description Pure, dependency-FREE Secretary rules shared across the browser. Two security/behaviour
|
|
4
|
+
* boundaries that also exist server-side and MUST stay identical:
|
|
5
|
+
* - routeRoutineStep(step, bands) — band-gate a Routine step (act → run · draft|ask → confirm · off → skip).
|
|
6
|
+
* - sanitizeProposedQuickActions(raw, …) — the dynamic-quick-action security gate (only prompt|compose, never run).
|
|
7
|
+
* These MIRROR `src/services/secretary-tick.ts` (the browser can't import the TS server module, and the
|
|
8
|
+
* server can't import an importmap `/js/...` module). Because this file has ZERO imports, the E2E harness
|
|
9
|
+
* (run via tsx, which doesn't type-check `test/`) imports it DIRECTLY and runs the same vectors through it
|
|
10
|
+
* AND the TS helpers — so e2e-secretary's parity test PROVES the two copies agree (gap G6). Keep both
|
|
11
|
+
* sides byte-equivalent; if you change one, change the other and the parity vectors will confirm it.
|
|
12
|
+
* @structure BANDS · routeRoutineStep · sanitizeProposedQuickActions
|
|
13
|
+
* @usage import { routeRoutineStep, sanitizeProposedQuickActions } from '/js/services/secretary-rules.js';
|
|
14
|
+
* @version-history
|
|
15
|
+
* v0.1.0 — 2026-06-28 — G6: extract the cross-runtime rules to a pure shared module + parity test.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Autonomy bands, least → most restrictive. */
|
|
19
|
+
export const BANDS = ['act', 'draft', 'ask', 'off'];
|
|
20
|
+
|
|
21
|
+
/** Inputs a `compose` quick action may focus (must match the working cards the view renders). */
|
|
22
|
+
const QUICK_COMPOSE_TARGETS = new Set(['plan', 'find', 'note']);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Band-gate a Routine step. A valid step-level `band` override wins, else the policy band, else the
|
|
26
|
+
* conservative 'ask'. act → run · draft|ask → confirm · off → skip. (Mirror of secretary-tick.ts.)
|
|
27
|
+
*/
|
|
28
|
+
export function routeRoutineStep(step, bands) {
|
|
29
|
+
const capability = String((step && step.capability) != null ? step.capability : '').trim();
|
|
30
|
+
const override = (step && typeof step.band === 'string' && BANDS.includes(step.band)) ? step.band : null;
|
|
31
|
+
const band = override != null ? override
|
|
32
|
+
: ((bands && typeof bands[capability] === 'string') ? bands[capability] : 'ask');
|
|
33
|
+
const disposition = band === 'off' ? 'skip' : band === 'act' ? 'run' : 'confirm';
|
|
34
|
+
return { band, disposition };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sanitize brain-seeded / secretary-proposed quick actions — the SECURITY boundary: a non-core action may
|
|
39
|
+
* ONLY be 'prompt' (a canned chat message) or 'compose' (focus an input) — never a 'run' verb. Malformed
|
|
40
|
+
* entries are dropped. Returns normalized actions WITHOUT id/createdAt (callers stamp those). (Mirror of
|
|
41
|
+
* secretary-tick.ts::sanitizeProposedQuickActions.)
|
|
42
|
+
*/
|
|
43
|
+
export function sanitizeProposedQuickActions(raw, source, status = 'proposed') {
|
|
44
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const r of list) {
|
|
47
|
+
if (!r || typeof r !== 'object') continue;
|
|
48
|
+
const label = String(r.label != null ? r.label : '').trim().slice(0, 40);
|
|
49
|
+
const kind = String(r.kind != null ? r.kind : '').trim();
|
|
50
|
+
if (!label) continue;
|
|
51
|
+
if (kind === 'compose') {
|
|
52
|
+
const target = String(r.target != null ? r.target : '').trim();
|
|
53
|
+
if (!QUICK_COMPOSE_TARGETS.has(target)) continue;
|
|
54
|
+
out.push({ label, kind: 'compose', target, source, status });
|
|
55
|
+
} else if (kind === 'prompt') {
|
|
56
|
+
const prompt = String(r.prompt != null ? r.prompt : '').trim().slice(0, 500);
|
|
57
|
+
if (!prompt) continue;
|
|
58
|
+
out.push({ label, kind: 'prompt', prompt, source, status });
|
|
59
|
+
}
|
|
60
|
+
// kind === 'run' (or anything else) → dropped: the explicit security boundary.
|
|
61
|
+
}
|
|
62
|
+
return out.slice(0, 6);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** True iff a stored quick action is allowed (a well-formed prompt|compose, never a run verb). Derived
|
|
66
|
+
* from sanitizeProposedQuickActions so the rule has a single definition. Used to scrub config server-side. */
|
|
67
|
+
export function isAllowedQuickAction(a) {
|
|
68
|
+
// status doesn't affect validity (only kind/label/target/prompt do) — pass a fixed valid status.
|
|
69
|
+
return sanitizeProposedQuickActions([a], (a && a.source) || '', 'active').length === 1;
|
|
70
|
+
}
|