aimeat 1.33.2 → 1.35.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 +437 -15
- package/dist/locales/fi.json +437 -15
- 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 +464 -2
- package/dist/public/js/i18n.js +9 -0
- 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 +266 -17
- package/dist/public/js/services/secretary-policy.js +28 -2
- 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 +13 -4
- package/dist/public/views/profile/agents/state-detector.js +6 -0
- package/dist/public/views/profile/agents/tab-integration.js +11 -2
- 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 +217 -79
- package/dist/public/views/secretary/dashboard.js +617 -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 +836 -44
- 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/cli/connect/mcp/tools/organisms.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +9 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/onboarding-prompt.d.ts +3 -0
- package/dist/src/cli/connect/onboarding-prompt.d.ts.map +1 -1
- package/dist/src/cli/connect/onboarding-prompt.js +10 -3
- package/dist/src/cli/connect/onboarding-prompt.js.map +1 -1
- 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 +689 -7
- 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/agent-onboarding.d.ts +4 -0
- package/dist/src/mcp/agent-onboarding.d.ts.map +1 -1
- package/dist/src/mcp/agent-onboarding.js +28 -2
- package/dist/src/mcp/agent-onboarding.js.map +1 -1
- 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/agent-onboarding-schemas.d.ts +47 -0
- package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -1
- package/dist/src/models/agent-onboarding-schemas.js +117 -0
- package/dist/src/models/agent-onboarding-schemas.js.map +1 -1
- package/dist/src/models/offer-schemas.d.ts +8 -8
- package/dist/src/routes/agent-onboarding.d.ts +5 -0
- package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
- package/dist/src/routes/agent-onboarding.js +21 -2
- package/dist/src/routes/agent-onboarding.js.map +1 -1
- 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 +34 -0
- package/dist/src/routes/secretary.d.ts.map +1 -0
- package/dist/src/routes/secretary.js +311 -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/onboarding-guide.d.ts +53 -0
- package/dist/src/services/onboarding-guide.d.ts.map +1 -0
- package/dist/src/services/onboarding-guide.js +81 -0
- package/dist/src/services/onboarding-guide.js.map +1 -0
- 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 +50 -2
- package/dist/src/services/scheduler.d.ts.map +1 -1
- package/dist/src/services/scheduler.js +454 -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/secretary-workbrief.d.ts +144 -0
- package/dist/src/services/secretary-workbrief.d.ts.map +1 -0
- package/dist/src/services/secretary-workbrief.js +315 -0
- package/dist/src/services/secretary-workbrief.js.map +1 -0
- package/dist/src/services/secretary-worktier.d.ts +118 -0
- package/dist/src/services/secretary-worktier.d.ts.map +1 -0
- package/dist/src/services/secretary-worktier.js +157 -0
- package/dist/src/services/secretary-worktier.js.map +1 -0
- package/dist/src/services/skill-bundle/generator.d.ts +3 -0
- package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
- package/dist/src/services/skill-bundle/generator.js +19 -0
- package/dist/src/services/skill-bundle/generator.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
|
@@ -5,6 +5,27 @@
|
|
|
5
5
|
* Supports special @activate trigger: runs on extension activation AND every server startup.
|
|
6
6
|
* Every execution creates an ExecutionLogEntry with timing, result, and memory I/O.
|
|
7
7
|
* @version-history
|
|
8
|
+
* v2.13.0 — 2026-07-01 — Light-vs-heavy seam: the tick no longer cheap-authors content (the one-shot
|
|
9
|
+
* authoring in the now-removed secretary-authoring.ts hallucinated). It now detects STRUCTURAL gaps
|
|
10
|
+
* (empty/thin workspaces, stale content, off-target KPIs) and proposes grounded Work Briefs a Builder
|
|
11
|
+
* (a connected big AI via the appdev MCP tools) executes — proposeWorkBriefs in services/
|
|
12
|
+
* secretary-workbrief.ts (deterministic, no paid call, deduped against open briefs). The cheap
|
|
13
|
+
* Secretary watches + hands off; it never authors structure/data.
|
|
14
|
+
* v2.12.0 — 2026-06-30 — Learning loop closes: (A) selectLessons threads the highest-signal reviewed-
|
|
15
|
+
* decision scores into the tick's briefing/action prompt (repeat what worked, avoid what didn't);
|
|
16
|
+
* (B) proposeLearnedRule — when a cluster of reviewed decisions scored poorly, the tick asks for ONE
|
|
17
|
+
* durable operating rule and posts it as a GATED inbox card (brain-rule) the owner approves; never
|
|
18
|
+
* auto-applied. Source decisions flagged proposalUsed so a cluster doesn't re-propose.
|
|
19
|
+
* v2.11.0 — 2026-06-30 — Secretary autonomous authoring: a band-gated phase (`author_content`) fills
|
|
20
|
+
* the active context's EMPTY workspaces — document spaces first, then records — toward the focus
|
|
21
|
+
* milestone (services/secretary-authoring.ts). act → write + publish · draft/ask → leave drafts ·
|
|
22
|
+
* off (default) → skip. Metered per workspace, capped by the same per-day morsel budget.
|
|
23
|
+
* v2.10.0 — 2026-06-28 — Secretary Strategy: the tick reads the active context's optional `strategy`
|
|
24
|
+
* (current → target + principles/risks + ordered milestones) and steers the briefing toward the
|
|
25
|
+
* current focus milestone (first not-yet-reached), respecting the principles + flagging risks.
|
|
26
|
+
* v2.9.0 — 2026-06-28 — Secretary bound triggers: a fired trigger can carry out a BOUND action —
|
|
27
|
+
* start an Agent Workflow run, or queue + run a specialist task (its prompt) node-side this tick —
|
|
28
|
+
* instead of only posting a reminder (fireBoundTriggerAction). Gated by the existing spend guards.
|
|
8
29
|
* v2.8.0 — 2026-06-24 — Secretary P5 (S-C): scheduled extension jobs decrypt `type: secret` config
|
|
9
30
|
* before the sandbox VM, and an instance-scoped job loads the instance's (decrypted) config so a
|
|
10
31
|
* cron sync uses the same bring-your-own-key secret a live action would. See extension-secrets.ts.
|
|
@@ -22,6 +43,9 @@
|
|
|
22
43
|
* v2.5.0 — 2026-06-24 — Add the `secretary` kind: the Secretary's autonomous tick (Phase 4) — runs
|
|
23
44
|
* the active context's brain on the owner's key and appends a briefing to `secretary.feed`;
|
|
24
45
|
* stop-spending skips the paid call.
|
|
46
|
+
* v2.5.1 — 2026-06-29 — Secretary tick: pre-flight AI-availability guard — when the owner has no usable
|
|
47
|
+
* AI key, skip the tick gracefully (like stop-spending/budget) instead of erroring every run once the
|
|
48
|
+
* "daily check-in always briefs" change removed the idle early-out.
|
|
25
49
|
* v1.0.0 — 2026-03-01 — Initial implementation with croner
|
|
26
50
|
* v2.0.0 — 2026-03-15 — Add @activate trigger, execution log, memory access tracking
|
|
27
51
|
* v2.1.0 — 2026-06-05 — executeJob/triggerNow return a JobOutcome so a manual
|
|
@@ -42,11 +66,17 @@ import { executeExtensionAction, trackMemoryAccess } from './extension-runtime.j
|
|
|
42
66
|
import { getEncryptionKey } from './encryption.js';
|
|
43
67
|
import { getExtSecretKeys, getInstanceSecretKeys, decryptSecretFields } from './extension-secrets.js';
|
|
44
68
|
import { completeForOwner } from './ai-completion.js';
|
|
69
|
+
import { proposeWorkBriefs as buildContextBriefs, resolveCompletedBriefs } from './secretary-workbrief.js';
|
|
70
|
+
import { notify } from './notify.js';
|
|
71
|
+
import { appendSecretaryFeed } from './secretary-feed.js';
|
|
72
|
+
import { produceClarifyDeliverable } from './secretary-clarify.js';
|
|
73
|
+
import { listSpecialists } from './specialist.js';
|
|
74
|
+
import { runAllSpecialists, runSpecialistQueue } from './specialist-runner.js';
|
|
45
75
|
import { getActiveWorkflowEngine } from './workflow/engine.js';
|
|
46
76
|
import { getActiveConnectTunnelManager } from './connect-tunnel.js';
|
|
47
77
|
import { parseGaiiLoose, buildGEAI } from '../utils/gaii.js';
|
|
48
78
|
import { evaluateConstraints, applyAfterRun } from './schedule-constraints.js';
|
|
49
|
-
import { classifySecretaryActions,
|
|
79
|
+
import { classifySecretaryActions, actionKind, ledgerSpentToday, budgetExceeded, bumpLedger, routeTickNote, deriveRoutineActions, actionItemKey, routineDueForRearm, rearmRoutine, isAllowedQuickAction, evaluateTriggers, settleFiredTrigger, selectLessons, poorDecisionCluster } from './secretary-tick.js';
|
|
50
80
|
import { emitChange } from './event-bus.js';
|
|
51
81
|
import { emitResourceUpdated } from '../mcp/index.js';
|
|
52
82
|
import { logger } from '../utils/logger.js';
|
|
@@ -499,6 +529,14 @@ export class Scheduler {
|
|
|
499
529
|
this.notifyOwner(job, 'Secretary paused for today', `daily budget reached (${spentToday}/${budget} morsels)`);
|
|
500
530
|
return { reads: ['secretary.config'], writes: [], skipped: true, skipReason: `daily morsel budget reached (${spentToday}/${budget})` };
|
|
501
531
|
}
|
|
532
|
+
// (2.5) AI-availability guard: the autonomous tick's core output is a PAID AI briefing — the daily
|
|
533
|
+
// check-in always briefs (there is no "nothing to do" early-out). If the owner has no usable AI key,
|
|
534
|
+
// no paid call can run this tick, so skip gracefully — exactly like the stop-spending / budget guards
|
|
535
|
+
// above — instead of letting completeForOwner throw NO_API_KEY and marking EVERY tick "failed" (which
|
|
536
|
+
// also fires a "Schedule failed" notification each run). The tick resumes once a key is set in Settings.
|
|
537
|
+
if (!(await this.ownerHasUsableAiKey(owner))) {
|
|
538
|
+
return { reads: ['secretary.config', 'openrouter.apikey'], writes: [], skipped: true, skipReason: 'no AI key configured' };
|
|
539
|
+
}
|
|
502
540
|
// (3) Cheap "anything to do?" pre-check (P1-B): no open goals + no due decisions → skip the paid call.
|
|
503
541
|
const goalRecs = await this.storage.listMemory(owner, { prefix: 'secretary.goal.' });
|
|
504
542
|
const openGoals = goalRecs
|
|
@@ -509,12 +547,74 @@ export class Scheduler {
|
|
|
509
547
|
const dueDecisions = decRecs
|
|
510
548
|
.map((r) => (r.value ?? {}))
|
|
511
549
|
.filter((d) => d.status === 'open' && typeof d.revisitWhen === 'string' && Date.parse(d.revisitWhen) <= nowMs);
|
|
512
|
-
|
|
513
|
-
|
|
550
|
+
// (3a) G2: re-arm any RECURRING routine whose cadence has elapsed since its last completed run —
|
|
551
|
+
// reset its steps to 'pending' + re-activate it (FREE, no AI). Applied to the working `active` so the
|
|
552
|
+
// advancement pass below picks up the freshly-pending steps; persisted via persistTickState.
|
|
553
|
+
const rearmNowIso = new Date().toISOString();
|
|
554
|
+
const rearmNowMs = Date.now();
|
|
555
|
+
const rearmedRoutines = [];
|
|
556
|
+
if (Array.isArray(active.routines)) {
|
|
557
|
+
active.routines = active.routines.map((r) => {
|
|
558
|
+
if (r.id && routineDueForRearm(r, rearmNowMs)) {
|
|
559
|
+
const armed = rearmRoutine(r, rearmNowIso, rearmNowMs);
|
|
560
|
+
rearmedRoutines.push({ id: r.id, routine: armed });
|
|
561
|
+
return armed;
|
|
562
|
+
}
|
|
563
|
+
return r;
|
|
564
|
+
});
|
|
514
565
|
}
|
|
566
|
+
// (3a2) Triggers (Slice 2): evaluate the owner's triggers — fire any that are due (time/recurring),
|
|
567
|
+
// whose bound target completed, or whose condition is met. FREE (no AI). Signals from memory.
|
|
568
|
+
const trigRecs = await this.storage.listMemory(owner, { prefix: 'secretary.trigger.' });
|
|
569
|
+
const triggers = trigRecs.map((r) => (r.value ?? {})).filter((tr) => tr && tr.id);
|
|
570
|
+
let firedTriggers = [];
|
|
571
|
+
if (triggers.length) {
|
|
572
|
+
const completed = {};
|
|
573
|
+
for (const r of goalRecs) {
|
|
574
|
+
const g = (r.value ?? {});
|
|
575
|
+
if (g.id)
|
|
576
|
+
completed[String(g.id)] = g.status === 'done';
|
|
577
|
+
}
|
|
578
|
+
for (const c of contexts)
|
|
579
|
+
for (const rt of (c.routines || []))
|
|
580
|
+
if (rt.id)
|
|
581
|
+
completed[rt.id] = rt.status === 'done';
|
|
582
|
+
const memoryCounts = {};
|
|
583
|
+
const lastActivityMs = {};
|
|
584
|
+
const prefixes = Array.from(new Set(triggers.filter((tr) => tr.kind === 'condition' && tr.condition?.prefix).map((tr) => tr.condition.prefix)));
|
|
585
|
+
for (const pfx of prefixes) {
|
|
586
|
+
const recs = await this.storage.listMemory(owner, { prefix: pfx });
|
|
587
|
+
memoryCounts[pfx] = recs.length;
|
|
588
|
+
let newest = 0;
|
|
589
|
+
for (const r of recs) {
|
|
590
|
+
const ts = Date.parse(r.updatedAt || '');
|
|
591
|
+
if (!Number.isNaN(ts) && ts > newest)
|
|
592
|
+
newest = ts;
|
|
593
|
+
}
|
|
594
|
+
if (newest)
|
|
595
|
+
lastActivityMs[pfx] = newest;
|
|
596
|
+
}
|
|
597
|
+
firedTriggers = evaluateTriggers(triggers, { completed, memoryCounts, lastActivityMs }, nowMs).map((f) => f.trigger);
|
|
598
|
+
}
|
|
599
|
+
// (3a2) Clarify jobs the owner may have answered: the Secretary asked questions instead of guessing;
|
|
600
|
+
// once answered (in the inbox) the tick produces the deliverable grounded in those answers.
|
|
601
|
+
const clarifyRecs = await this.storage.listMemory(owner, { prefix: 'secretary.clarify.' });
|
|
602
|
+
const askedClarify = clarifyRecs.map((r) => (r.value ?? {})).filter((j) => j && j.status === 'asked');
|
|
603
|
+
// (3a3) Specialists the Secretary directs: any with QUEUED tasks have node-run work to do this tick.
|
|
604
|
+
const specialistAgents = await listSpecialists(this.storage, owner.split('@')[0]);
|
|
605
|
+
const hasSpecialistWork = specialistAgents.length > 0
|
|
606
|
+
&& (await Promise.all(specialistAgents.map((s) => this.storage.listAgentTasks(s.gaii, { status: 'queued', perPage: 1 })))).some((r) => r.total > 0);
|
|
607
|
+
// (3b) B5: band-driven routine advancement (FREE — no AI). Decide, from each active routine's next
|
|
608
|
+
// step band, what the tick should auto-run (act-band file steps) vs surface as a follow-up action-item.
|
|
609
|
+
const routineActions = deriveRoutineActions(active);
|
|
610
|
+
// The daily check-in always produces a briefing + proposed next actions (its whole purpose) — it never
|
|
611
|
+
// skips for "nothing to do". Cost is bounded by the earlier stop-spending + daily-budget guards.
|
|
515
612
|
const ownerName = owner.split('@')[0];
|
|
516
613
|
const writes = [];
|
|
517
614
|
const newPending = {};
|
|
615
|
+
// B5: routine step completions (act-band file steps the tick ran) + the action-items it derived.
|
|
616
|
+
const stepCompletions = [];
|
|
617
|
+
let newActionItems = [];
|
|
518
618
|
let paidMorsels = 0;
|
|
519
619
|
try {
|
|
520
620
|
// Learning loop (Phase 5): score any decisions whose revisit time passed (each = 1 morsel).
|
|
@@ -525,10 +625,76 @@ export class Scheduler {
|
|
|
525
625
|
await this.appendFeed(owner, { kind: 'review', contextId: active.id, contextName: active.name || '', text: `Reviewed ${reviewed.length} decision(s):\n${summary}` });
|
|
526
626
|
writes.push(...reviewed.map((r) => `secretary.decision.${r.id}`));
|
|
527
627
|
}
|
|
628
|
+
// Learning loop (B): when a cluster of reviewed decisions scored poorly, propose ONE durable
|
|
629
|
+
// operating rule — GATED: posted as an inbox card the owner approves; never auto-applied to the brain.
|
|
630
|
+
const proposal = await this.proposeLearnedRule(owner, ownerName, active, cfg.pendingDecisions ?? {});
|
|
631
|
+
if (proposal) {
|
|
632
|
+
newPending[proposal.id] = proposal.pending;
|
|
633
|
+
paidMorsels += 1;
|
|
634
|
+
writes.push('agent-message', ...proposal.writes);
|
|
635
|
+
}
|
|
636
|
+
const wsList = await this.loadContextWorkspaces(owner, active);
|
|
637
|
+
// Resume any answered clarify jobs: produce the deliverable grounded in the owner's answers (+ facts).
|
|
638
|
+
const clarifyWrites = await this.resumeClarifyJobs(owner, ownerName, askedClarify);
|
|
639
|
+
writes.push(...clarifyWrites);
|
|
640
|
+
paidMorsels += clarifyWrites.filter((w) => w.startsWith('organism.')).length;
|
|
641
|
+
// Direct the specialists: run each specialist's queued tasks node-side (owner key + quality contract).
|
|
642
|
+
if (hasSpecialistWork) {
|
|
643
|
+
const specialistWrites = await runAllSpecialists(this.storage, this.config, owner, ownerName);
|
|
644
|
+
if (specialistWrites.length) {
|
|
645
|
+
writes.push(...specialistWrites);
|
|
646
|
+
paidMorsels += specialistWrites.length;
|
|
647
|
+
await this.appendFeed(owner, { kind: 'act', contextId: active.id, contextName: active.name || '', text: `🧩 Specialists produced ${specialistWrites.length} deliverable(s)` });
|
|
648
|
+
writes.push('secretary.feed');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// B5 routine pass (FREE — no AI): auto-run each act-band performable step + mark it done, and stamp
|
|
652
|
+
// the derived follow-up action-items. draft/ask/delegate steps were already turned into action-items
|
|
653
|
+
// by deriveRoutineActions — the owner handles those from the dashboard. G4: the step's kind comes
|
|
654
|
+
// from actionKind (note caps file a note; briefing/reminders append a feed entry) — performSecretaryAct
|
|
655
|
+
// carries out the right one, so briefing/reminders are FED here exactly as in the legacy action-loop.
|
|
656
|
+
const nowIso = new Date().toISOString();
|
|
657
|
+
for (const fs of routineActions.runSteps) {
|
|
658
|
+
const routine = (active.routines || []).find((r) => r.id === fs.routineId);
|
|
659
|
+
const step = (routine?.steps || []).find((s) => s.id === fs.stepId);
|
|
660
|
+
if (!step)
|
|
661
|
+
continue;
|
|
662
|
+
const kind = actionKind(String(step.capability)); // 'note' | 'feed'
|
|
663
|
+
const a = { capability: String(step.capability), summary: String(step.summary || ''), payload: { note: String(step.summary || '') }, kind, band: 'act' };
|
|
664
|
+
writes.push(...await this.performSecretaryAct(owner, active, a, wsList));
|
|
665
|
+
const verb = kind === 'feed' ? 'Surfaced' : 'Filed';
|
|
666
|
+
stepCompletions.push({ routineId: fs.routineId, stepId: fs.stepId, status: 'done', summary: `${verb}: ${String(step.summary || '')}`.slice(0, 200) });
|
|
667
|
+
}
|
|
668
|
+
newActionItems = routineActions.items.map((it) => ({
|
|
669
|
+
id: 'ai-' + randomUUID().slice(0, 8), labelKind: it.labelKind, summary: it.summary, suggestedAction: it.suggestedAction, source: it.source, createdAt: nowIso, status: 'open',
|
|
670
|
+
}));
|
|
671
|
+
// Fire triggers (Slice 2 + bound actions): each fired trigger either just REMINDS (feed entry +
|
|
672
|
+
// action-item, the default) or carries out a BOUND action — start an Agent Workflow run, or queue +
|
|
673
|
+
// run a task for one of the owner's specialists. A bound action that ran does the work, so no
|
|
674
|
+
// follow-up action-item is added (the feed records what happened). Then the trigger settles
|
|
675
|
+
// (recurring re-arms with a new nextFireAt; one-off/completion/condition → 'fired'). Paid bound
|
|
676
|
+
// actions (workflow/specialist) are metered like any autonomous spend; the earlier stop-spending +
|
|
677
|
+
// daily-budget guards already gate this whole block.
|
|
678
|
+
for (const tr of firedTriggers) {
|
|
679
|
+
const label = String(tr.label || tr.action?.summary || 'Trigger').slice(0, 200);
|
|
680
|
+
const bound = await this.fireBoundTriggerAction(owner, ownerName, tr, label, specialistAgents, writes);
|
|
681
|
+
await this.appendFeed(owner, { kind: 'trigger', contextId: tr.contextId || active.id, contextName: active.name || '', text: bound ? `⏰ ${label} → ${bound.note}` : `⏰ ${label}` });
|
|
682
|
+
if (bound)
|
|
683
|
+
paidMorsels += bound.morsels;
|
|
684
|
+
else
|
|
685
|
+
newActionItems.push({ id: 'ai-' + randomUUID().slice(0, 8), text: label, suggestedAction: { kind: 'advance', routineId: '' }, source: 'trigger:' + tr.id, createdAt: nowIso, status: 'open' });
|
|
686
|
+
const settled = settleFiredTrigger(tr, nowMs);
|
|
687
|
+
const key = `secretary.trigger.${tr.id}`;
|
|
688
|
+
const ex = trigRecs.find((r) => r.key === key);
|
|
689
|
+
await this.storage.setMemory({ key, ownerGaii: owner, value: settled, visibility: 'private', tags: ['secretary', 'trigger', String(settled.status || '')], ttlHours: null, version: ex ? ex.version + 1 : 1, createdAt: ex?.createdAt ?? nowIso, updatedAt: nowIso });
|
|
690
|
+
writes.push(key);
|
|
691
|
+
}
|
|
528
692
|
// Action generation (P1-A): ask the model for a STRUCTURED action list tied to the open goals +
|
|
529
693
|
// a bounded slice of the self-organism (the context's workspaces). 1 paid morsel.
|
|
530
|
-
|
|
531
|
-
|
|
694
|
+
// Learning loop (A): thread the highest-signal reviewed-decision lessons into the prompt so the next
|
|
695
|
+
// cycle repeats what scored well and avoids what scored poorly (the scores aren't a dead-end record).
|
|
696
|
+
const lessons = selectLessons(decRecs.map((r) => (r.value ?? {})), active.id);
|
|
697
|
+
const { systemPrompt, prompt } = this.buildTickPrompt(ownerName, active, openGoals, wsList, lessons);
|
|
532
698
|
const result = await completeForOwner(this.storage, this.config, owner, { prompt, systemPrompt, appId: `schedule:${job.id}` });
|
|
533
699
|
paidMorsels += 1;
|
|
534
700
|
const parsed = extractJsonObject(result.content);
|
|
@@ -576,19 +742,64 @@ export class Scheduler {
|
|
|
576
742
|
const feedText = deferred.length ? `${briefingText}\n\n${deferred.join('\n')}` : briefingText;
|
|
577
743
|
await this.appendFeed(owner, { kind: 'briefing', contextId: active.id, contextName: active.name || '', text: feedText });
|
|
578
744
|
writes.push('secretary.feed');
|
|
745
|
+
// Heavy work is DISPATCHED, not invented here. Instead of asking the cheap model to author content
|
|
746
|
+
// one-shot (which hallucinated and "went onto the Builders' turf"), the tick detects STRUCTURAL gaps
|
|
747
|
+
// (empty/thin workspaces, stale content, off-target KPIs) and proposes a grounded Work Brief a Builder
|
|
748
|
+
// — a connected big AI via the appdev MCP tools — executes (the owner pastes it into Claude Desktop,
|
|
749
|
+
// or dispatches it to a connected agent). Brief assembly is deterministic (no paid call), idempotent
|
|
750
|
+
// (deduped against open briefs by gap), and bounded per tick. The cheap Secretary never authors data.
|
|
751
|
+
// Self-heal first: a dispatched brief whose Builder task finished flips to 'done' so it leaves the tray.
|
|
752
|
+
await resolveCompletedBriefs(this.storage, owner).catch(() => 0);
|
|
753
|
+
const briefed = await this.proposeWorkBriefs(owner, ownerName, active, wsList, openGoals);
|
|
754
|
+
if (briefed.writes.length) {
|
|
755
|
+
writes.push(...briefed.writes);
|
|
756
|
+
for (const line of briefed.feedLines) {
|
|
757
|
+
await this.appendFeed(owner, { kind: 'briefing', contextId: active.id, contextName: active.name || '', text: line, href: '/v1/secretary' });
|
|
758
|
+
}
|
|
759
|
+
writes.push('secretary.feed');
|
|
760
|
+
emitChange('agents', owner);
|
|
761
|
+
// Notify the owner (the header bell) when NEW heavy work is ready to hand off — so a brief the tick
|
|
762
|
+
// proposes overnight is actively surfaced, not just discovered on the next visit. Best-effort.
|
|
763
|
+
if (briefed.briefs.length) {
|
|
764
|
+
await notify(this.storage, owner, {
|
|
765
|
+
type: 'secretary_work_brief',
|
|
766
|
+
title: briefed.briefs.length === 1 ? '1 work brief ready' : `${briefed.briefs.length} work briefs ready`,
|
|
767
|
+
body: 'Heavy work the Secretary prepared — hand it to a Builder.',
|
|
768
|
+
link: '/v1/secretary',
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
579
772
|
}
|
|
580
773
|
finally {
|
|
581
|
-
// Persist the spend ledger (+ any new pending decisions
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
774
|
+
// Persist the spend ledger (+ any new pending decisions, routine step completions, and derived
|
|
775
|
+
// action-items) even if generation threw mid-way, so the soft budget reflects what was actually
|
|
776
|
+
// spent. Read-modify-write a fresh config to avoid clobber.
|
|
777
|
+
if (paidMorsels > 0 || Object.keys(newPending).length || stepCompletions.length || newActionItems.length || rearmedRoutines.length) {
|
|
778
|
+
await this.persistTickState(owner, active.id, today, paidMorsels, newPending, stepCompletions, newActionItems, rearmedRoutines);
|
|
585
779
|
writes.push('secretary.config');
|
|
586
780
|
}
|
|
587
781
|
}
|
|
588
782
|
if (Object.keys(newPending).length)
|
|
589
783
|
emitChange('agent-messages');
|
|
784
|
+
if (stepCompletions.length || newActionItems.length || rearmedRoutines.length)
|
|
785
|
+
emitChange('agent-tasks', owner);
|
|
590
786
|
return { reads: ['secretary.config', 'secretary.goal.*', 'secretary.decision.*'], writes };
|
|
591
787
|
}
|
|
788
|
+
/**
|
|
789
|
+
* True when the owner has a usable AI key for autonomous completions. Mirrors the key resolution in
|
|
790
|
+
* completeForOwner(): the default 'openrouter' provider REQUIRES an encrypted key; other providers
|
|
791
|
+
* (e.g. a local model) may run keyless. Used as the Secretary tick's pre-flight guard so the tick
|
|
792
|
+
* skips rather than errors when no paid call is possible.
|
|
793
|
+
*/
|
|
794
|
+
async ownerHasUsableAiKey(owner) {
|
|
795
|
+
const [keyRec, prefsRec] = await Promise.all([
|
|
796
|
+
this.storage.getMemory(owner, 'openrouter.apikey'),
|
|
797
|
+
this.storage.getMemory(owner, 'openrouter.settings'),
|
|
798
|
+
]);
|
|
799
|
+
const provider = prefsRec?.value?.provider || 'openrouter';
|
|
800
|
+
const hasKey = !!keyRec?.value?.encrypted;
|
|
801
|
+
return provider !== 'openrouter' || hasKey;
|
|
802
|
+
}
|
|
592
803
|
/** Read the active context's workspaces (id + name) from its self-organism registry. */
|
|
593
804
|
async loadContextWorkspaces(owner, active) {
|
|
594
805
|
if (!active.organismId)
|
|
@@ -597,15 +808,53 @@ export class Scheduler {
|
|
|
597
808
|
const list = (rec?.value?.workspaces) ?? [];
|
|
598
809
|
return list.filter((w) => w && w.id).map((w) => ({ id: w.id, name: w.name || w.id }));
|
|
599
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* Detect structural gaps in the active context's workspaces and propose Work Briefs for a Builder to
|
|
813
|
+
* execute — heavy work is DISPATCHED, never invented by the cheap Secretary. Deterministic + idempotent:
|
|
814
|
+
* a gap already covered by an OPEN brief (proposed/dispatched) is skipped, so repeated ticks converge
|
|
815
|
+
* instead of spamming. Bounded to MAX_NEW_BRIEFS new briefs per tick. No paid AI call (storage reads +
|
|
816
|
+
* best-effort Discovery only). Briefs are persisted as secretary.brief.{id}; the view's brief tray reads
|
|
817
|
+
* them and offers "Open in Claude Desktop" / "Send to a connected Builder".
|
|
818
|
+
*/
|
|
819
|
+
async proposeWorkBriefs(owner, ownerName, active, wsList, openGoals) {
|
|
820
|
+
const briefContext = { id: active.id, name: active.name, organismId: active.organismId, brain: active.brain, strategy: active.strategy };
|
|
821
|
+
const { writes, briefs } = await buildContextBriefs(this.storage, this.config, {
|
|
822
|
+
ownerGhii: owner, ownerName, active: briefContext, wsList, openGoals,
|
|
823
|
+
});
|
|
824
|
+
const feedLines = briefs.map((b) => `📋 Work brief ready — ${b.objective} Open it in the Secretary to hand to a Builder.`);
|
|
825
|
+
return { writes, feedLines, briefs };
|
|
826
|
+
}
|
|
600
827
|
/** Build the tick's system + user prompt asking for a STRUCTURED action list (P1-A). */
|
|
601
|
-
buildTickPrompt(ownerName, active, openGoals, wsList) {
|
|
828
|
+
buildTickPrompt(ownerName, active, openGoals, wsList, lessons = []) {
|
|
602
829
|
const rules = (active.brain?.rules || []).map((r) => '- ' + (r.description || '')).join('\n');
|
|
603
830
|
const systemPrompt = `You are ${ownerName}'s personal Secretary, working in the "${active.name || 'personal'}" context.\n${active.brain?.purpose || ''}\n\nOperating rules:\n${rules || '(none)'}\n\nYou are doing an autonomous check-in for the owner (they are not present). Reply in the owner's language. Return ONLY a JSON object — no prose around it.`;
|
|
604
831
|
const goalLines = openGoals.length
|
|
605
832
|
? openGoals.map((g) => `- ${String(g.title || '')}${g.why ? ` (why: ${String(g.why)})` : ''}`).join('\n')
|
|
606
833
|
: '(no open goals)';
|
|
607
834
|
const wsNames = wsList.map((w) => w.name).join(', ') || '(none yet)';
|
|
608
|
-
|
|
835
|
+
// Optional strategy steering: if this context has a strategy, give the model the current → target,
|
|
836
|
+
// the principles/risks to respect, and the CURRENT FOCUS milestone (first not-yet-reached) so the
|
|
837
|
+
// briefing steers toward it and flags when it looks reached. Free text; never invents progress.
|
|
838
|
+
let strategyBlock = '';
|
|
839
|
+
const strat = active.strategy;
|
|
840
|
+
if (strat && strat.enabled) {
|
|
841
|
+
const msl = Array.isArray(strat.milestones) ? strat.milestones : [];
|
|
842
|
+
const focus = msl.find((m) => m && m.status !== 'reached');
|
|
843
|
+
const principles = (strat.principles || []).filter(Boolean);
|
|
844
|
+
const risks = (strat.risks || []).filter(Boolean);
|
|
845
|
+
strategyBlock = `\n\nStrategy for this area:\n- Current state: ${strat.current || '(unstated)'}\n- Target state: ${strat.target || '(unstated)'}\n- Current focus milestone: ${focus ? `${focus.title}${focus.enables ? ` (enables: ${focus.enables})` : ''}` : '(all milestones reached — suggest what the next target could be)'}`
|
|
846
|
+
+ (principles.length ? `\n- Principles to respect: ${principles.join('; ')}` : '')
|
|
847
|
+
+ (risks.length ? `\n- Risks to watch: ${risks.join('; ')}` : '')
|
|
848
|
+
+ `\nSteer the briefing toward this focus milestone, respect the principles, and flag a risk if it is materialising. If recent activity suggests the milestone is reached, say so and point at the next one.`;
|
|
849
|
+
}
|
|
850
|
+
// Learning loop (A): the highest-signal lessons from past decisions the owner's reviews scored —
|
|
851
|
+
// repeat what worked, avoid what didn't. Free text; the model weighs them, nothing is auto-applied.
|
|
852
|
+
let lessonsBlock = '';
|
|
853
|
+
if (lessons.length) {
|
|
854
|
+
lessonsBlock = `\n\nLessons from your recently reviewed decisions (let these steer you — lean on what scored well, avoid repeating what scored poorly):\n`
|
|
855
|
+
+ lessons.map((l) => `- [${l.score}/100] ${l.decision}${l.verdict ? ` — ${l.verdict}` : ''}`).join('\n');
|
|
856
|
+
}
|
|
857
|
+
const prompt = `Open goals in this context:\n${goalLines}\n\nThe owner's filing space "${active.organismName || active.name}" has these workspaces: ${wsNames}.${strategyBlock}${lessonsBlock}\n\nDecide what to do for the owner right now. Return a JSON object EXACTLY like:\n{\n "briefing": "2-4 sentence check-in: what to focus on and anything needing attention",\n "actions": [\n { "capability": "file_intake", "summary": "short label", "payload": { "workspace": "<an existing workspace name>", "note": "the text to file" } },\n { "capability": "reminders", "summary": "a concrete reminder for the owner", "payload": {} }\n ]\n}\nRules for actions: propose 0-3 CONCRETE actions that move the open goals forward. Only use these capabilities: "file_intake"/"curate_knowledge" (file a note into a workspace — set payload.workspace to an existing workspace name and payload.note to the text) and "reminders"/"briefing" (surface a note to the owner — payload may be empty). Do not invent other capabilities. If nothing concrete is warranted, return an empty actions array.`;
|
|
609
858
|
return { systemPrompt, prompt };
|
|
610
859
|
}
|
|
611
860
|
/** Best-match a workspace by (possibly fuzzy) name; falls back to the first workspace. */
|
|
@@ -623,6 +872,37 @@ export class Scheduler {
|
|
|
623
872
|
}
|
|
624
873
|
return wsList[0];
|
|
625
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* Resume answered clarify jobs (ask-don't-hallucinate): for each job whose inbox questions the owner
|
|
877
|
+
* answered, produce the deliverable grounded ONLY in the stored facts + those answers, file it into the
|
|
878
|
+
* job's workspace, reply the outcome back into the thread, and mark the job done. Unanswered jobs are
|
|
879
|
+
* left untouched. Produce logic is shared with the live DM responder (services/secretary-clarify.ts).
|
|
880
|
+
*/
|
|
881
|
+
async resumeClarifyJobs(owner, ownerName, jobs) {
|
|
882
|
+
const writes = [];
|
|
883
|
+
if (!jobs.length)
|
|
884
|
+
return writes;
|
|
885
|
+
const secretaryGaii = `secretary#${ownerName}@${this.config.nodeId}`;
|
|
886
|
+
// Local agent→owner delivery never touches federation peers, so an empty map is sufficient here.
|
|
887
|
+
const ctx = { config: this.config, storage: this.storage, peers: new Map() };
|
|
888
|
+
for (const job of jobs) {
|
|
889
|
+
const conversationId = job.conversationId;
|
|
890
|
+
const questionId = job.questionId;
|
|
891
|
+
if (!conversationId || !questionId)
|
|
892
|
+
continue;
|
|
893
|
+
const thread = await this.storage.listAgentDmThread(secretaryGaii, conversationId, { perPage: 50 }).catch(() => null);
|
|
894
|
+
const msgs = thread?.messages || [];
|
|
895
|
+
const ans = msgs.find((m) => m.interactive?.role === 'answers' && m.interactive.answersFor === questionId);
|
|
896
|
+
if (!ans || ans.interactive?.role !== 'answers')
|
|
897
|
+
continue; // not answered yet
|
|
898
|
+
const { writes: w } = await produceClarifyDeliverable(ctx, {
|
|
899
|
+
ownerGhii: owner, ownerName, agentGaii: secretaryGaii,
|
|
900
|
+
job: job, answers: ans.interactive.answers,
|
|
901
|
+
});
|
|
902
|
+
writes.push(...w);
|
|
903
|
+
}
|
|
904
|
+
return writes;
|
|
905
|
+
}
|
|
626
906
|
/**
|
|
627
907
|
* Perform an `act`-band action: file a note (note kind) or append a feed entry (feed kind). `ctx` is
|
|
628
908
|
* the destination context — normally the active context, but for a G1 auto-routed note it is the
|
|
@@ -644,7 +924,7 @@ export class Scheduler {
|
|
|
644
924
|
const text = routedFrom
|
|
645
925
|
? `Routed a note to ${ctx.name || ctx.id} → ${ws.name}: ${a.summary}`
|
|
646
926
|
: `Filed a note → ${ws.name}: ${a.summary}`;
|
|
647
|
-
await this.appendFeed(owner, { kind: 'act', contextId: ctx.id, contextName: ctx.name || '', text });
|
|
927
|
+
await this.appendFeed(owner, { kind: 'act', contextId: ctx.id, contextName: ctx.name || '', text, href: `/v1/profile?tab=organisms&org=${ctx.organismId}&ws=${ws.id}` });
|
|
648
928
|
return [key, 'secretary.feed'];
|
|
649
929
|
}
|
|
650
930
|
}
|
|
@@ -658,6 +938,69 @@ export class Scheduler {
|
|
|
658
938
|
const map = rec?.value?.map;
|
|
659
939
|
return (map && typeof map === 'object') ? map : {};
|
|
660
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Learning loop (B): when a CLUSTER of reviewed decisions in this context scored poorly, ask the model
|
|
943
|
+
* for ONE durable operating rule that would avoid repeating them, and post it as a GATED inbox decision
|
|
944
|
+
* card (type 'brain-rule'). The owner approves it in the inbox (applyDecision appends it to the brain) —
|
|
945
|
+
* it is NEVER auto-applied. The source decisions are flagged `proposalUsed` so the same cluster doesn't
|
|
946
|
+
* re-propose every tick. Returns the pending card (+ the keys written) or null when there isn't enough
|
|
947
|
+
* signal / a brain-rule proposal is already pending / the model returns nothing. One paid morsel on propose.
|
|
948
|
+
*/
|
|
949
|
+
async proposeLearnedRule(owner, ownerName, active, existingPending) {
|
|
950
|
+
// One brain-rule proposal at a time — don't stack cards.
|
|
951
|
+
if (Object.values(existingPending).some((p) => p?.type === 'brain-rule'))
|
|
952
|
+
return null;
|
|
953
|
+
const recs = await this.storage.listMemory(owner, { prefix: 'secretary.decision.' });
|
|
954
|
+
const byId = new Map();
|
|
955
|
+
const values = [];
|
|
956
|
+
for (const r of recs) {
|
|
957
|
+
const d = (r.value ?? {});
|
|
958
|
+
if (d.id)
|
|
959
|
+
byId.set(String(d.id), r);
|
|
960
|
+
values.push(d);
|
|
961
|
+
}
|
|
962
|
+
const cluster = poorDecisionCluster(values, active.id);
|
|
963
|
+
if (!cluster.length)
|
|
964
|
+
return null;
|
|
965
|
+
const secretaryGaii = `secretary#${ownerName}@${this.config.nodeId}`;
|
|
966
|
+
const agent = await this.storage.getAgent(secretaryGaii);
|
|
967
|
+
if (!agent)
|
|
968
|
+
return null;
|
|
969
|
+
const systemPrompt = `You distil ONE durable operating rule for ${ownerName}'s Secretary from past decisions that turned out poorly. Reply in the owner's language. Return ONLY a JSON object: {"rule": "one concrete, actionable sentence the Secretary should follow from now on"}.`;
|
|
970
|
+
const prompt = `These past decisions scored poorly on review:\n${cluster.map((d, i) => `${i + 1}. ${String(d.decision || '')} — chosen: ${String(d.chosen || '')} — verdict: ${String(d.verdict || '')} (${d.score}/100)`).join('\n')}\n\nPropose ONE operating rule that, followed from now on, would avoid repeating these. Make it concrete and general enough to reuse — not a restatement of a single case.`;
|
|
971
|
+
let rule;
|
|
972
|
+
try {
|
|
973
|
+
const out = await completeForOwner(this.storage, this.config, owner, { prompt, systemPrompt, appId: 'schedule:learn:rule' });
|
|
974
|
+
rule = String(extractJsonObject(out.content)?.rule ?? '').trim().slice(0, 400);
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
if (!rule)
|
|
980
|
+
return null;
|
|
981
|
+
// Flag the source decisions so the cluster doesn't re-propose every tick.
|
|
982
|
+
const now = new Date().toISOString();
|
|
983
|
+
const writes = [];
|
|
984
|
+
for (const d of cluster) {
|
|
985
|
+
const rec = byId.get(String(d.id));
|
|
986
|
+
if (!rec)
|
|
987
|
+
continue;
|
|
988
|
+
await this.storage.setMemory({ key: rec.key, ownerGaii: owner, value: { ...d, proposalUsed: true }, visibility: 'private', tags: rec.tags ?? ['secretary', 'decision', 'reviewed', active.id], ttlHours: null, version: rec.version + 1, createdAt: rec.createdAt, updatedAt: now });
|
|
989
|
+
writes.push(rec.key);
|
|
990
|
+
}
|
|
991
|
+
const promptId = 'learn-' + randomUUID().slice(0, 8);
|
|
992
|
+
const question = `I noticed ${cluster.length} past decisions that went poorly. Add this rule to my brain so I avoid repeating them?\n\n"${rule}"`;
|
|
993
|
+
const pending = { type: 'brain-rule', rule, contextId: active.id, contextName: active.name || '', basisCount: cluster.length, question, createdAt: now };
|
|
994
|
+
const record = {
|
|
995
|
+
id: randomUUID(), agentGaii: secretaryGaii, threadId: randomUUID(), direction: 'outbound', senderGaii: owner,
|
|
996
|
+
content: `Proposed operating rule: ${rule}`, status: 'delivered',
|
|
997
|
+
metadata: { prompt: { promptId, question, options: ['Yes, add it', 'No'], allowOther: false } }, createdAt: now,
|
|
998
|
+
};
|
|
999
|
+
await this.storage.createMessage(record);
|
|
1000
|
+
await this.appendFeed(owner, { kind: 'review', contextId: active.id, contextName: active.name || '', text: `💡 Proposed a learned rule from ${cluster.length} reviewed decisions — approve it in your inbox.` });
|
|
1001
|
+
writes.push('secretary.feed');
|
|
1002
|
+
return { id: promptId, pending, writes };
|
|
1003
|
+
}
|
|
661
1004
|
/**
|
|
662
1005
|
* Post a draft/ask-band action as an inbox decision card (reusing the Phase-3b prompt rails) and
|
|
663
1006
|
* return the stashed pending action keyed by promptId. Note actions reuse the existing `file-note`
|
|
@@ -699,31 +1042,69 @@ export class Scheduler {
|
|
|
699
1042
|
await this.storage.createMessage(record);
|
|
700
1043
|
return { id: promptId, pending };
|
|
701
1044
|
}
|
|
702
|
-
/**
|
|
703
|
-
|
|
1045
|
+
/**
|
|
1046
|
+
* Read-modify-write secretary.config to persist the day's spend ledger + any new pending decisions,
|
|
1047
|
+
* plus (B5) routine step completions the tick performed and the action-items it derived. Applied to a
|
|
1048
|
+
* FRESH read (targeted by routine/step id) to avoid clobbering concurrent view edits.
|
|
1049
|
+
*/
|
|
1050
|
+
async persistTickState(owner, contextId, today, morsels, newPending, stepCompletions = [], newActionItems = [], rearmedRoutines = []) {
|
|
704
1051
|
const rec = await this.storage.getMemory(owner, 'secretary.config');
|
|
705
1052
|
const cfg = (rec?.value ?? {});
|
|
706
1053
|
const ledger = morsels > 0 ? bumpLedger(cfg.autonomousLedger, contextId, today, morsels) : cfg.autonomousLedger;
|
|
707
1054
|
const pending = Object.keys(newPending).length ? { ...(cfg.pendingDecisions ?? {}), ...newPending } : cfg.pendingDecisions;
|
|
708
|
-
const next = { ...cfg, autonomousLedger: ledger, pendingDecisions: pending };
|
|
709
1055
|
const now = new Date().toISOString();
|
|
1056
|
+
// B5/G2: apply routine re-arms + step completions + merge action-items; G8: scrub quick actions.
|
|
1057
|
+
let contexts = cfg.contexts;
|
|
1058
|
+
if (Array.isArray(cfg.contexts)) {
|
|
1059
|
+
contexts = cfg.contexts.map((c) => {
|
|
1060
|
+
if (c.id !== contextId)
|
|
1061
|
+
return c;
|
|
1062
|
+
// G8 defense-in-depth: drop any disallowed (e.g. smuggled 'run') quick action server-side.
|
|
1063
|
+
const quickActions = Array.isArray(c.quickActions) ? c.quickActions.filter(isAllowedQuickAction) : c.quickActions;
|
|
1064
|
+
let routines = c.routines;
|
|
1065
|
+
// G2: replace re-armed routines first (steps reset to pending) so a same-tick file-step
|
|
1066
|
+
// completion below lands on the freshly-armed step.
|
|
1067
|
+
if (rearmedRoutines.length && Array.isArray(routines)) {
|
|
1068
|
+
routines = routines.map((r) => {
|
|
1069
|
+
const armed = rearmedRoutines.find((x) => x.id === r.id);
|
|
1070
|
+
return armed ? armed.routine : r;
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
if (stepCompletions.length && Array.isArray(routines)) {
|
|
1074
|
+
routines = routines.map((r) => {
|
|
1075
|
+
const comps = stepCompletions.filter((sc) => sc.routineId === r.id);
|
|
1076
|
+
if (!comps.length)
|
|
1077
|
+
return r;
|
|
1078
|
+
const steps = (r.steps || []).map((s) => {
|
|
1079
|
+
const comp = comps.find((x) => x.stepId === s.id);
|
|
1080
|
+
return comp ? { ...s, status: comp.status, result: { summary: comp.summary, ts: now } } : s;
|
|
1081
|
+
});
|
|
1082
|
+
// B4 fix: a 'delegated' step is NOT settled (its task is still out) — the routine stays active.
|
|
1083
|
+
const settled = steps.every((s) => s.status !== 'pending' && s.status !== 'running' && s.status !== 'delegated');
|
|
1084
|
+
const results = [...comps.map((x) => ({ ts: now, summary: x.summary })), ...(r.results || [])].slice(0, 20);
|
|
1085
|
+
return { ...r, steps, results, lastRunAt: now, status: settled ? 'done' : r.status };
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
let actionItems = c.actionItems;
|
|
1089
|
+
if (newActionItems.length) {
|
|
1090
|
+
const existing = Array.isArray(c.actionItems) ? c.actionItems : [];
|
|
1091
|
+
const seenOpen = new Set(existing.filter((a) => a.status === 'open').map((a) => actionItemKey(a)));
|
|
1092
|
+
const fresh = newActionItems.filter((a) => !seenOpen.has(actionItemKey(a)));
|
|
1093
|
+
actionItems = [...fresh, ...existing].slice(0, 50);
|
|
1094
|
+
}
|
|
1095
|
+
return { ...c, routines, actionItems, quickActions };
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
const next = { ...cfg, contexts, autonomousLedger: ledger, pendingDecisions: pending };
|
|
710
1099
|
await this.storage.setMemory({
|
|
711
1100
|
key: 'secretary.config', ownerGaii: owner, value: next, visibility: 'private', tags: ['secretary', 'config'],
|
|
712
1101
|
ttlHours: null, version: rec ? rec.version + 1 : 1, createdAt: rec?.createdAt ?? now, updatedAt: now,
|
|
713
1102
|
});
|
|
714
1103
|
}
|
|
715
|
-
/** Append one entry to the owner's Home feed (`secretary.feed`, newest first, capped
|
|
1104
|
+
/** Append one entry to the owner's Home feed (`secretary.feed`, newest first, capped). Delegates to the
|
|
1105
|
+
* shared helper so the live DM responder / clarify-produce write the SAME feed. */
|
|
716
1106
|
async appendFeed(owner, entry) {
|
|
717
|
-
|
|
718
|
-
const existing = await this.storage.getMemory(owner, feedKey);
|
|
719
|
-
const list = Array.isArray(existing?.value?.items)
|
|
720
|
-
? existing.value.items : [];
|
|
721
|
-
const now = new Date().toISOString();
|
|
722
|
-
const items = [{ id: randomUUID(), ts: now, ...entry }, ...list].slice(0, 50);
|
|
723
|
-
await this.storage.setMemory({
|
|
724
|
-
key: feedKey, ownerGaii: owner, value: { items }, visibility: 'private', tags: ['secretary', 'feed'],
|
|
725
|
-
ttlHours: null, version: existing ? existing.version + 1 : 1, createdAt: existing?.createdAt ?? now, updatedAt: now,
|
|
726
|
-
});
|
|
1107
|
+
await appendSecretaryFeed(this.storage, owner, entry);
|
|
727
1108
|
}
|
|
728
1109
|
/**
|
|
729
1110
|
* Learning-loop review sweep (Phase 5): find open decision-log contracts whose `revisitWhen` has
|
|
@@ -766,6 +1147,53 @@ export class Scheduler {
|
|
|
766
1147
|
}
|
|
767
1148
|
return done;
|
|
768
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Carry out a fired trigger's BOUND action (if any). Returns null for a plain reminder (the caller then
|
|
1152
|
+
* posts a follow-up action-item); otherwise `{ note, morsels }` describing what ran (the caller folds the
|
|
1153
|
+
* note into the feed entry and meters the morsels). Two bindings:
|
|
1154
|
+
* - workflow: start the named Agent Workflow run via the deterministic engine.
|
|
1155
|
+
* - specialist: queue a task (the trigger's `prompt`, defaulting to its label) for the named specialist
|
|
1156
|
+
* and run it node-side now (owner key + quality contract), so the deliverable lands this same tick.
|
|
1157
|
+
* Best-effort: any failure (unknown workflow/specialist, engine down, spend-pause) degrades to a plain
|
|
1158
|
+
* reminder so a misconfigured binding never breaks the tick.
|
|
1159
|
+
*/
|
|
1160
|
+
async fireBoundTriggerAction(owner, ownerName, tr, label, specialists, writes) {
|
|
1161
|
+
const act = tr.action || {};
|
|
1162
|
+
try {
|
|
1163
|
+
if (act.kind === 'workflow' && act.workflowId) {
|
|
1164
|
+
const engine = getActiveWorkflowEngine();
|
|
1165
|
+
if (!engine)
|
|
1166
|
+
return null;
|
|
1167
|
+
const r = await engine.startRun(owner, ownerName, String(act.workflowId), { mode: 'full-live' });
|
|
1168
|
+
if ('error' in r) {
|
|
1169
|
+
logger.info('[secretary tick] bound workflow refused to start', { id: tr.id, wf: act.workflowId, err: r.error.join('; ') });
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
writes.push(`workflows.run.${act.workflowId}.${r.runId}`);
|
|
1173
|
+
return { note: `▶️ ${act.workflowId}`, morsels: 1 };
|
|
1174
|
+
}
|
|
1175
|
+
if (act.kind === 'specialist' && act.specialistName) {
|
|
1176
|
+
const spec = specialists.find((s) => s.name === act.specialistName);
|
|
1177
|
+
if (!spec)
|
|
1178
|
+
return null;
|
|
1179
|
+
const now = new Date().toISOString();
|
|
1180
|
+
await this.storage.createAgentTask({
|
|
1181
|
+
id: randomUUID(), agentGaii: spec.gaii, ownerGaii: owner,
|
|
1182
|
+
title: label, description: String(act.prompt || label).slice(0, 4000),
|
|
1183
|
+
scope: [], rules: [], verification: { userExpects: '', technicalChecks: [] }, todos: [],
|
|
1184
|
+
status: 'queued', parentTaskId: `trigger:${tr.id}`, createdAt: now, updatedAt: now,
|
|
1185
|
+
});
|
|
1186
|
+
const specWrites = await runSpecialistQueue(this.storage, this.config, owner, spec, { limit: 1 });
|
|
1187
|
+
writes.push(...specWrites);
|
|
1188
|
+
// 1 morsel for the specialist run even if it deferred (kept the queued task) — the run was attempted.
|
|
1189
|
+
return { note: `🧩 ${spec.name}`, morsels: Math.max(1, specWrites.length) };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
catch (e) {
|
|
1193
|
+
logger.info('[secretary tick] bound trigger action failed', { id: tr.id, kind: act.kind, err: e.message });
|
|
1194
|
+
}
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
769
1197
|
/**
|
|
770
1198
|
* `agent_task` kind: materialise an AgentTaskRecord into the target agent's
|
|
771
1199
|
* queue and wake it via the existing webhook/MCP/SSE fan-out. The schedule is
|