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
|
@@ -5,6 +5,21 @@
|
|
|
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.12.0 — 2026-06-30 — Learning loop closes: (A) selectLessons threads the highest-signal reviewed-
|
|
9
|
+
* decision scores into the tick's briefing/action prompt (repeat what worked, avoid what didn't);
|
|
10
|
+
* (B) proposeLearnedRule — when a cluster of reviewed decisions scored poorly, the tick asks for ONE
|
|
11
|
+
* durable operating rule and posts it as a GATED inbox card (brain-rule) the owner approves; never
|
|
12
|
+
* auto-applied. Source decisions flagged proposalUsed so a cluster doesn't re-propose.
|
|
13
|
+
* v2.11.0 — 2026-06-30 — Secretary autonomous authoring: a band-gated phase (`author_content`) fills
|
|
14
|
+
* the active context's EMPTY workspaces — document spaces first, then records — toward the focus
|
|
15
|
+
* milestone (services/secretary-authoring.ts). act → write + publish · draft/ask → leave drafts ·
|
|
16
|
+
* off (default) → skip. Metered per workspace, capped by the same per-day morsel budget.
|
|
17
|
+
* v2.10.0 — 2026-06-28 — Secretary Strategy: the tick reads the active context's optional `strategy`
|
|
18
|
+
* (current → target + principles/risks + ordered milestones) and steers the briefing toward the
|
|
19
|
+
* current focus milestone (first not-yet-reached), respecting the principles + flagging risks.
|
|
20
|
+
* v2.9.0 — 2026-06-28 — Secretary bound triggers: a fired trigger can carry out a BOUND action —
|
|
21
|
+
* start an Agent Workflow run, or queue + run a specialist task (its prompt) node-side this tick —
|
|
22
|
+
* instead of only posting a reminder (fireBoundTriggerAction). Gated by the existing spend guards.
|
|
8
23
|
* v2.8.0 — 2026-06-24 — Secretary P5 (S-C): scheduled extension jobs decrypt `type: secret` config
|
|
9
24
|
* before the sandbox VM, and an instance-scoped job loads the instance's (decrypted) config so a
|
|
10
25
|
* cron sync uses the same bring-your-own-key secret a live action would. See extension-secrets.ts.
|
|
@@ -22,6 +37,9 @@
|
|
|
22
37
|
* v2.5.0 — 2026-06-24 — Add the `secretary` kind: the Secretary's autonomous tick (Phase 4) — runs
|
|
23
38
|
* the active context's brain on the owner's key and appends a briefing to `secretary.feed`;
|
|
24
39
|
* stop-spending skips the paid call.
|
|
40
|
+
* v2.5.1 — 2026-06-29 — Secretary tick: pre-flight AI-availability guard — when the owner has no usable
|
|
41
|
+
* AI key, skip the tick gracefully (like stop-spending/budget) instead of erroring every run once the
|
|
42
|
+
* "daily check-in always briefs" change removed the idle early-out.
|
|
25
43
|
* v1.0.0 — 2026-03-01 — Initial implementation with croner
|
|
26
44
|
* v2.0.0 — 2026-03-15 — Add @activate trigger, execution log, memory access tracking
|
|
27
45
|
* v2.1.0 — 2026-06-05 — executeJob/triggerNow return a JobOutcome so a manual
|
|
@@ -42,11 +60,16 @@ import { executeExtensionAction, trackMemoryAccess } from './extension-runtime.j
|
|
|
42
60
|
import { getEncryptionKey } from './encryption.js';
|
|
43
61
|
import { getExtSecretKeys, getInstanceSecretKeys, decryptSecretFields } from './extension-secrets.js';
|
|
44
62
|
import { completeForOwner } from './ai-completion.js';
|
|
63
|
+
import { authorWorkspaceContent } from './secretary-authoring.js';
|
|
64
|
+
import { appendSecretaryFeed } from './secretary-feed.js';
|
|
65
|
+
import { produceClarifyDeliverable } from './secretary-clarify.js';
|
|
66
|
+
import { listSpecialists } from './specialist.js';
|
|
67
|
+
import { runAllSpecialists, runSpecialistQueue } from './specialist-runner.js';
|
|
45
68
|
import { getActiveWorkflowEngine } from './workflow/engine.js';
|
|
46
69
|
import { getActiveConnectTunnelManager } from './connect-tunnel.js';
|
|
47
70
|
import { parseGaiiLoose, buildGEAI } from '../utils/gaii.js';
|
|
48
71
|
import { evaluateConstraints, applyAfterRun } from './schedule-constraints.js';
|
|
49
|
-
import { classifySecretaryActions,
|
|
72
|
+
import { classifySecretaryActions, actionKind, ledgerSpentToday, budgetExceeded, bumpLedger, routeTickNote, deriveRoutineActions, actionItemKey, routineDueForRearm, rearmRoutine, isAllowedQuickAction, evaluateTriggers, settleFiredTrigger, selectLessons, poorDecisionCluster } from './secretary-tick.js';
|
|
50
73
|
import { emitChange } from './event-bus.js';
|
|
51
74
|
import { emitResourceUpdated } from '../mcp/index.js';
|
|
52
75
|
import { logger } from '../utils/logger.js';
|
|
@@ -499,6 +522,14 @@ export class Scheduler {
|
|
|
499
522
|
this.notifyOwner(job, 'Secretary paused for today', `daily budget reached (${spentToday}/${budget} morsels)`);
|
|
500
523
|
return { reads: ['secretary.config'], writes: [], skipped: true, skipReason: `daily morsel budget reached (${spentToday}/${budget})` };
|
|
501
524
|
}
|
|
525
|
+
// (2.5) AI-availability guard: the autonomous tick's core output is a PAID AI briefing — the daily
|
|
526
|
+
// check-in always briefs (there is no "nothing to do" early-out). If the owner has no usable AI key,
|
|
527
|
+
// no paid call can run this tick, so skip gracefully — exactly like the stop-spending / budget guards
|
|
528
|
+
// above — instead of letting completeForOwner throw NO_API_KEY and marking EVERY tick "failed" (which
|
|
529
|
+
// also fires a "Schedule failed" notification each run). The tick resumes once a key is set in Settings.
|
|
530
|
+
if (!(await this.ownerHasUsableAiKey(owner))) {
|
|
531
|
+
return { reads: ['secretary.config', 'openrouter.apikey'], writes: [], skipped: true, skipReason: 'no AI key configured' };
|
|
532
|
+
}
|
|
502
533
|
// (3) Cheap "anything to do?" pre-check (P1-B): no open goals + no due decisions → skip the paid call.
|
|
503
534
|
const goalRecs = await this.storage.listMemory(owner, { prefix: 'secretary.goal.' });
|
|
504
535
|
const openGoals = goalRecs
|
|
@@ -509,12 +540,74 @@ export class Scheduler {
|
|
|
509
540
|
const dueDecisions = decRecs
|
|
510
541
|
.map((r) => (r.value ?? {}))
|
|
511
542
|
.filter((d) => d.status === 'open' && typeof d.revisitWhen === 'string' && Date.parse(d.revisitWhen) <= nowMs);
|
|
512
|
-
|
|
513
|
-
|
|
543
|
+
// (3a) G2: re-arm any RECURRING routine whose cadence has elapsed since its last completed run —
|
|
544
|
+
// reset its steps to 'pending' + re-activate it (FREE, no AI). Applied to the working `active` so the
|
|
545
|
+
// advancement pass below picks up the freshly-pending steps; persisted via persistTickState.
|
|
546
|
+
const rearmNowIso = new Date().toISOString();
|
|
547
|
+
const rearmNowMs = Date.now();
|
|
548
|
+
const rearmedRoutines = [];
|
|
549
|
+
if (Array.isArray(active.routines)) {
|
|
550
|
+
active.routines = active.routines.map((r) => {
|
|
551
|
+
if (r.id && routineDueForRearm(r, rearmNowMs)) {
|
|
552
|
+
const armed = rearmRoutine(r, rearmNowIso, rearmNowMs);
|
|
553
|
+
rearmedRoutines.push({ id: r.id, routine: armed });
|
|
554
|
+
return armed;
|
|
555
|
+
}
|
|
556
|
+
return r;
|
|
557
|
+
});
|
|
514
558
|
}
|
|
559
|
+
// (3a2) Triggers (Slice 2): evaluate the owner's triggers — fire any that are due (time/recurring),
|
|
560
|
+
// whose bound target completed, or whose condition is met. FREE (no AI). Signals from memory.
|
|
561
|
+
const trigRecs = await this.storage.listMemory(owner, { prefix: 'secretary.trigger.' });
|
|
562
|
+
const triggers = trigRecs.map((r) => (r.value ?? {})).filter((tr) => tr && tr.id);
|
|
563
|
+
let firedTriggers = [];
|
|
564
|
+
if (triggers.length) {
|
|
565
|
+
const completed = {};
|
|
566
|
+
for (const r of goalRecs) {
|
|
567
|
+
const g = (r.value ?? {});
|
|
568
|
+
if (g.id)
|
|
569
|
+
completed[String(g.id)] = g.status === 'done';
|
|
570
|
+
}
|
|
571
|
+
for (const c of contexts)
|
|
572
|
+
for (const rt of (c.routines || []))
|
|
573
|
+
if (rt.id)
|
|
574
|
+
completed[rt.id] = rt.status === 'done';
|
|
575
|
+
const memoryCounts = {};
|
|
576
|
+
const lastActivityMs = {};
|
|
577
|
+
const prefixes = Array.from(new Set(triggers.filter((tr) => tr.kind === 'condition' && tr.condition?.prefix).map((tr) => tr.condition.prefix)));
|
|
578
|
+
for (const pfx of prefixes) {
|
|
579
|
+
const recs = await this.storage.listMemory(owner, { prefix: pfx });
|
|
580
|
+
memoryCounts[pfx] = recs.length;
|
|
581
|
+
let newest = 0;
|
|
582
|
+
for (const r of recs) {
|
|
583
|
+
const ts = Date.parse(r.updatedAt || '');
|
|
584
|
+
if (!Number.isNaN(ts) && ts > newest)
|
|
585
|
+
newest = ts;
|
|
586
|
+
}
|
|
587
|
+
if (newest)
|
|
588
|
+
lastActivityMs[pfx] = newest;
|
|
589
|
+
}
|
|
590
|
+
firedTriggers = evaluateTriggers(triggers, { completed, memoryCounts, lastActivityMs }, nowMs).map((f) => f.trigger);
|
|
591
|
+
}
|
|
592
|
+
// (3a2) Clarify jobs the owner may have answered: the Secretary asked questions instead of guessing;
|
|
593
|
+
// once answered (in the inbox) the tick produces the deliverable grounded in those answers.
|
|
594
|
+
const clarifyRecs = await this.storage.listMemory(owner, { prefix: 'secretary.clarify.' });
|
|
595
|
+
const askedClarify = clarifyRecs.map((r) => (r.value ?? {})).filter((j) => j && j.status === 'asked');
|
|
596
|
+
// (3a3) Specialists the Secretary directs: any with QUEUED tasks have node-run work to do this tick.
|
|
597
|
+
const specialistAgents = await listSpecialists(this.storage, owner.split('@')[0]);
|
|
598
|
+
const hasSpecialistWork = specialistAgents.length > 0
|
|
599
|
+
&& (await Promise.all(specialistAgents.map((s) => this.storage.listAgentTasks(s.gaii, { status: 'queued', perPage: 1 })))).some((r) => r.total > 0);
|
|
600
|
+
// (3b) B5: band-driven routine advancement (FREE — no AI). Decide, from each active routine's next
|
|
601
|
+
// step band, what the tick should auto-run (act-band file steps) vs surface as a follow-up action-item.
|
|
602
|
+
const routineActions = deriveRoutineActions(active);
|
|
603
|
+
// The daily check-in always produces a briefing + proposed next actions (its whole purpose) — it never
|
|
604
|
+
// skips for "nothing to do". Cost is bounded by the earlier stop-spending + daily-budget guards.
|
|
515
605
|
const ownerName = owner.split('@')[0];
|
|
516
606
|
const writes = [];
|
|
517
607
|
const newPending = {};
|
|
608
|
+
// B5: routine step completions (act-band file steps the tick ran) + the action-items it derived.
|
|
609
|
+
const stepCompletions = [];
|
|
610
|
+
let newActionItems = [];
|
|
518
611
|
let paidMorsels = 0;
|
|
519
612
|
try {
|
|
520
613
|
// Learning loop (Phase 5): score any decisions whose revisit time passed (each = 1 morsel).
|
|
@@ -525,10 +618,76 @@ export class Scheduler {
|
|
|
525
618
|
await this.appendFeed(owner, { kind: 'review', contextId: active.id, contextName: active.name || '', text: `Reviewed ${reviewed.length} decision(s):\n${summary}` });
|
|
526
619
|
writes.push(...reviewed.map((r) => `secretary.decision.${r.id}`));
|
|
527
620
|
}
|
|
621
|
+
// Learning loop (B): when a cluster of reviewed decisions scored poorly, propose ONE durable
|
|
622
|
+
// operating rule — GATED: posted as an inbox card the owner approves; never auto-applied to the brain.
|
|
623
|
+
const proposal = await this.proposeLearnedRule(owner, ownerName, active, cfg.pendingDecisions ?? {});
|
|
624
|
+
if (proposal) {
|
|
625
|
+
newPending[proposal.id] = proposal.pending;
|
|
626
|
+
paidMorsels += 1;
|
|
627
|
+
writes.push('agent-message', ...proposal.writes);
|
|
628
|
+
}
|
|
629
|
+
const wsList = await this.loadContextWorkspaces(owner, active);
|
|
630
|
+
// Resume any answered clarify jobs: produce the deliverable grounded in the owner's answers (+ facts).
|
|
631
|
+
const clarifyWrites = await this.resumeClarifyJobs(owner, ownerName, askedClarify);
|
|
632
|
+
writes.push(...clarifyWrites);
|
|
633
|
+
paidMorsels += clarifyWrites.filter((w) => w.startsWith('organism.')).length;
|
|
634
|
+
// Direct the specialists: run each specialist's queued tasks node-side (owner key + quality contract).
|
|
635
|
+
if (hasSpecialistWork) {
|
|
636
|
+
const specialistWrites = await runAllSpecialists(this.storage, this.config, owner, ownerName);
|
|
637
|
+
if (specialistWrites.length) {
|
|
638
|
+
writes.push(...specialistWrites);
|
|
639
|
+
paidMorsels += specialistWrites.length;
|
|
640
|
+
await this.appendFeed(owner, { kind: 'act', contextId: active.id, contextName: active.name || '', text: `🧩 Specialists produced ${specialistWrites.length} deliverable(s)` });
|
|
641
|
+
writes.push('secretary.feed');
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// B5 routine pass (FREE — no AI): auto-run each act-band performable step + mark it done, and stamp
|
|
645
|
+
// the derived follow-up action-items. draft/ask/delegate steps were already turned into action-items
|
|
646
|
+
// by deriveRoutineActions — the owner handles those from the dashboard. G4: the step's kind comes
|
|
647
|
+
// from actionKind (note caps file a note; briefing/reminders append a feed entry) — performSecretaryAct
|
|
648
|
+
// carries out the right one, so briefing/reminders are FED here exactly as in the legacy action-loop.
|
|
649
|
+
const nowIso = new Date().toISOString();
|
|
650
|
+
for (const fs of routineActions.runSteps) {
|
|
651
|
+
const routine = (active.routines || []).find((r) => r.id === fs.routineId);
|
|
652
|
+
const step = (routine?.steps || []).find((s) => s.id === fs.stepId);
|
|
653
|
+
if (!step)
|
|
654
|
+
continue;
|
|
655
|
+
const kind = actionKind(String(step.capability)); // 'note' | 'feed'
|
|
656
|
+
const a = { capability: String(step.capability), summary: String(step.summary || ''), payload: { note: String(step.summary || '') }, kind, band: 'act' };
|
|
657
|
+
writes.push(...await this.performSecretaryAct(owner, active, a, wsList));
|
|
658
|
+
const verb = kind === 'feed' ? 'Surfaced' : 'Filed';
|
|
659
|
+
stepCompletions.push({ routineId: fs.routineId, stepId: fs.stepId, status: 'done', summary: `${verb}: ${String(step.summary || '')}`.slice(0, 200) });
|
|
660
|
+
}
|
|
661
|
+
newActionItems = routineActions.items.map((it) => ({
|
|
662
|
+
id: 'ai-' + randomUUID().slice(0, 8), labelKind: it.labelKind, summary: it.summary, suggestedAction: it.suggestedAction, source: it.source, createdAt: nowIso, status: 'open',
|
|
663
|
+
}));
|
|
664
|
+
// Fire triggers (Slice 2 + bound actions): each fired trigger either just REMINDS (feed entry +
|
|
665
|
+
// action-item, the default) or carries out a BOUND action — start an Agent Workflow run, or queue +
|
|
666
|
+
// run a task for one of the owner's specialists. A bound action that ran does the work, so no
|
|
667
|
+
// follow-up action-item is added (the feed records what happened). Then the trigger settles
|
|
668
|
+
// (recurring re-arms with a new nextFireAt; one-off/completion/condition → 'fired'). Paid bound
|
|
669
|
+
// actions (workflow/specialist) are metered like any autonomous spend; the earlier stop-spending +
|
|
670
|
+
// daily-budget guards already gate this whole block.
|
|
671
|
+
for (const tr of firedTriggers) {
|
|
672
|
+
const label = String(tr.label || tr.action?.summary || 'Trigger').slice(0, 200);
|
|
673
|
+
const bound = await this.fireBoundTriggerAction(owner, ownerName, tr, label, specialistAgents, writes);
|
|
674
|
+
await this.appendFeed(owner, { kind: 'trigger', contextId: tr.contextId || active.id, contextName: active.name || '', text: bound ? `⏰ ${label} → ${bound.note}` : `⏰ ${label}` });
|
|
675
|
+
if (bound)
|
|
676
|
+
paidMorsels += bound.morsels;
|
|
677
|
+
else
|
|
678
|
+
newActionItems.push({ id: 'ai-' + randomUUID().slice(0, 8), text: label, suggestedAction: { kind: 'advance', routineId: '' }, source: 'trigger:' + tr.id, createdAt: nowIso, status: 'open' });
|
|
679
|
+
const settled = settleFiredTrigger(tr, nowMs);
|
|
680
|
+
const key = `secretary.trigger.${tr.id}`;
|
|
681
|
+
const ex = trigRecs.find((r) => r.key === key);
|
|
682
|
+
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 });
|
|
683
|
+
writes.push(key);
|
|
684
|
+
}
|
|
528
685
|
// Action generation (P1-A): ask the model for a STRUCTURED action list tied to the open goals +
|
|
529
686
|
// a bounded slice of the self-organism (the context's workspaces). 1 paid morsel.
|
|
530
|
-
|
|
531
|
-
|
|
687
|
+
// Learning loop (A): thread the highest-signal reviewed-decision lessons into the prompt so the next
|
|
688
|
+
// cycle repeats what scored well and avoids what scored poorly (the scores aren't a dead-end record).
|
|
689
|
+
const lessons = selectLessons(decRecs.map((r) => (r.value ?? {})), active.id);
|
|
690
|
+
const { systemPrompt, prompt } = this.buildTickPrompt(ownerName, active, openGoals, wsList, lessons);
|
|
532
691
|
const result = await completeForOwner(this.storage, this.config, owner, { prompt, systemPrompt, appId: `schedule:${job.id}` });
|
|
533
692
|
paidMorsels += 1;
|
|
534
693
|
const parsed = extractJsonObject(result.content);
|
|
@@ -576,19 +735,62 @@ export class Scheduler {
|
|
|
576
735
|
const feedText = deferred.length ? `${briefingText}\n\n${deferred.join('\n')}` : briefingText;
|
|
577
736
|
await this.appendFeed(owner, { kind: 'briefing', contextId: active.id, contextName: active.name || '', text: feedText });
|
|
578
737
|
writes.push('secretary.feed');
|
|
738
|
+
// Autonomous authoring phase: actually FILL the workspaces (document-first). Band-gated on
|
|
739
|
+
// `author_content`: 'act' = write + publish · 'draft' = leave drafts for review · 'off' = skip.
|
|
740
|
+
// Each filled workspace is a paid AI call, so it respects the same per-day budget — pass the
|
|
741
|
+
// remaining headroom and let authoring stop when it's exhausted. OFF by default (it spends + can
|
|
742
|
+
// publish): a context that never opts in behaves exactly as before; the owner enables draft/act.
|
|
743
|
+
const authorBand = active.policy?.bands?.author_content ?? 'off';
|
|
744
|
+
if (authorBand !== 'off') {
|
|
745
|
+
const budgetRemaining = budget === null ? null : Math.max(0, budget - spentToday - paidMorsels);
|
|
746
|
+
if (budgetRemaining === null || budgetRemaining > 0) {
|
|
747
|
+
// Honour the owner's autoRetry/maxRetries so a flaky stealth model (owl-alpha intermittently 400s)
|
|
748
|
+
// is retried instead of killing the authoring on the first transient error. Off → no retry.
|
|
749
|
+
const aiPrefs = (await this.storage.getMemory(owner, 'openrouter.settings'))?.value;
|
|
750
|
+
const maxRetries = aiPrefs?.autoRetry === false ? 0 : (typeof aiPrefs?.maxRetries === 'number' ? aiPrefs.maxRetries : 3);
|
|
751
|
+
const authored = await authorWorkspaceContent(this.storage, this.config, owner, ownerName, active, wsList, {
|
|
752
|
+
openGoals, band: authorBand, aggressive: true, useGeneralKnowledge: true, budgetRemaining, maxRetries, appId: `schedule:${job.id}:author`,
|
|
753
|
+
});
|
|
754
|
+
paidMorsels += authored.morsels;
|
|
755
|
+
writes.push(...authored.writes);
|
|
756
|
+
for (const line of authored.summaries) {
|
|
757
|
+
await this.appendFeed(owner, { kind: 'act', contextId: active.id, contextName: active.name || '', text: line, href: `/v1/profile?tab=organisms&org=${active.organismId}` });
|
|
758
|
+
}
|
|
759
|
+
if (authored.summaries.length)
|
|
760
|
+
writes.push('secretary.feed');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
579
763
|
}
|
|
580
764
|
finally {
|
|
581
|
-
// Persist the spend ledger (+ any new pending decisions
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
765
|
+
// Persist the spend ledger (+ any new pending decisions, routine step completions, and derived
|
|
766
|
+
// action-items) even if generation threw mid-way, so the soft budget reflects what was actually
|
|
767
|
+
// spent. Read-modify-write a fresh config to avoid clobber.
|
|
768
|
+
if (paidMorsels > 0 || Object.keys(newPending).length || stepCompletions.length || newActionItems.length || rearmedRoutines.length) {
|
|
769
|
+
await this.persistTickState(owner, active.id, today, paidMorsels, newPending, stepCompletions, newActionItems, rearmedRoutines);
|
|
585
770
|
writes.push('secretary.config');
|
|
586
771
|
}
|
|
587
772
|
}
|
|
588
773
|
if (Object.keys(newPending).length)
|
|
589
774
|
emitChange('agent-messages');
|
|
775
|
+
if (stepCompletions.length || newActionItems.length || rearmedRoutines.length)
|
|
776
|
+
emitChange('agent-tasks', owner);
|
|
590
777
|
return { reads: ['secretary.config', 'secretary.goal.*', 'secretary.decision.*'], writes };
|
|
591
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* True when the owner has a usable AI key for autonomous completions. Mirrors the key resolution in
|
|
781
|
+
* completeForOwner(): the default 'openrouter' provider REQUIRES an encrypted key; other providers
|
|
782
|
+
* (e.g. a local model) may run keyless. Used as the Secretary tick's pre-flight guard so the tick
|
|
783
|
+
* skips rather than errors when no paid call is possible.
|
|
784
|
+
*/
|
|
785
|
+
async ownerHasUsableAiKey(owner) {
|
|
786
|
+
const [keyRec, prefsRec] = await Promise.all([
|
|
787
|
+
this.storage.getMemory(owner, 'openrouter.apikey'),
|
|
788
|
+
this.storage.getMemory(owner, 'openrouter.settings'),
|
|
789
|
+
]);
|
|
790
|
+
const provider = prefsRec?.value?.provider || 'openrouter';
|
|
791
|
+
const hasKey = !!keyRec?.value?.encrypted;
|
|
792
|
+
return provider !== 'openrouter' || hasKey;
|
|
793
|
+
}
|
|
592
794
|
/** Read the active context's workspaces (id + name) from its self-organism registry. */
|
|
593
795
|
async loadContextWorkspaces(owner, active) {
|
|
594
796
|
if (!active.organismId)
|
|
@@ -598,14 +800,36 @@ export class Scheduler {
|
|
|
598
800
|
return list.filter((w) => w && w.id).map((w) => ({ id: w.id, name: w.name || w.id }));
|
|
599
801
|
}
|
|
600
802
|
/** Build the tick's system + user prompt asking for a STRUCTURED action list (P1-A). */
|
|
601
|
-
buildTickPrompt(ownerName, active, openGoals, wsList) {
|
|
803
|
+
buildTickPrompt(ownerName, active, openGoals, wsList, lessons = []) {
|
|
602
804
|
const rules = (active.brain?.rules || []).map((r) => '- ' + (r.description || '')).join('\n');
|
|
603
805
|
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
806
|
const goalLines = openGoals.length
|
|
605
807
|
? openGoals.map((g) => `- ${String(g.title || '')}${g.why ? ` (why: ${String(g.why)})` : ''}`).join('\n')
|
|
606
808
|
: '(no open goals)';
|
|
607
809
|
const wsNames = wsList.map((w) => w.name).join(', ') || '(none yet)';
|
|
608
|
-
|
|
810
|
+
// Optional strategy steering: if this context has a strategy, give the model the current → target,
|
|
811
|
+
// the principles/risks to respect, and the CURRENT FOCUS milestone (first not-yet-reached) so the
|
|
812
|
+
// briefing steers toward it and flags when it looks reached. Free text; never invents progress.
|
|
813
|
+
let strategyBlock = '';
|
|
814
|
+
const strat = active.strategy;
|
|
815
|
+
if (strat && strat.enabled) {
|
|
816
|
+
const msl = Array.isArray(strat.milestones) ? strat.milestones : [];
|
|
817
|
+
const focus = msl.find((m) => m && m.status !== 'reached');
|
|
818
|
+
const principles = (strat.principles || []).filter(Boolean);
|
|
819
|
+
const risks = (strat.risks || []).filter(Boolean);
|
|
820
|
+
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)'}`
|
|
821
|
+
+ (principles.length ? `\n- Principles to respect: ${principles.join('; ')}` : '')
|
|
822
|
+
+ (risks.length ? `\n- Risks to watch: ${risks.join('; ')}` : '')
|
|
823
|
+
+ `\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.`;
|
|
824
|
+
}
|
|
825
|
+
// Learning loop (A): the highest-signal lessons from past decisions the owner's reviews scored —
|
|
826
|
+
// repeat what worked, avoid what didn't. Free text; the model weighs them, nothing is auto-applied.
|
|
827
|
+
let lessonsBlock = '';
|
|
828
|
+
if (lessons.length) {
|
|
829
|
+
lessonsBlock = `\n\nLessons from your recently reviewed decisions (let these steer you — lean on what scored well, avoid repeating what scored poorly):\n`
|
|
830
|
+
+ lessons.map((l) => `- [${l.score}/100] ${l.decision}${l.verdict ? ` — ${l.verdict}` : ''}`).join('\n');
|
|
831
|
+
}
|
|
832
|
+
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
833
|
return { systemPrompt, prompt };
|
|
610
834
|
}
|
|
611
835
|
/** Best-match a workspace by (possibly fuzzy) name; falls back to the first workspace. */
|
|
@@ -623,6 +847,37 @@ export class Scheduler {
|
|
|
623
847
|
}
|
|
624
848
|
return wsList[0];
|
|
625
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* Resume answered clarify jobs (ask-don't-hallucinate): for each job whose inbox questions the owner
|
|
852
|
+
* answered, produce the deliverable grounded ONLY in the stored facts + those answers, file it into the
|
|
853
|
+
* job's workspace, reply the outcome back into the thread, and mark the job done. Unanswered jobs are
|
|
854
|
+
* left untouched. Produce logic is shared with the live DM responder (services/secretary-clarify.ts).
|
|
855
|
+
*/
|
|
856
|
+
async resumeClarifyJobs(owner, ownerName, jobs) {
|
|
857
|
+
const writes = [];
|
|
858
|
+
if (!jobs.length)
|
|
859
|
+
return writes;
|
|
860
|
+
const secretaryGaii = `secretary#${ownerName}@${this.config.nodeId}`;
|
|
861
|
+
// Local agent→owner delivery never touches federation peers, so an empty map is sufficient here.
|
|
862
|
+
const ctx = { config: this.config, storage: this.storage, peers: new Map() };
|
|
863
|
+
for (const job of jobs) {
|
|
864
|
+
const conversationId = job.conversationId;
|
|
865
|
+
const questionId = job.questionId;
|
|
866
|
+
if (!conversationId || !questionId)
|
|
867
|
+
continue;
|
|
868
|
+
const thread = await this.storage.listAgentDmThread(secretaryGaii, conversationId, { perPage: 50 }).catch(() => null);
|
|
869
|
+
const msgs = thread?.messages || [];
|
|
870
|
+
const ans = msgs.find((m) => m.interactive?.role === 'answers' && m.interactive.answersFor === questionId);
|
|
871
|
+
if (!ans || ans.interactive?.role !== 'answers')
|
|
872
|
+
continue; // not answered yet
|
|
873
|
+
const { writes: w } = await produceClarifyDeliverable(ctx, {
|
|
874
|
+
ownerGhii: owner, ownerName, agentGaii: secretaryGaii,
|
|
875
|
+
job: job, answers: ans.interactive.answers,
|
|
876
|
+
});
|
|
877
|
+
writes.push(...w);
|
|
878
|
+
}
|
|
879
|
+
return writes;
|
|
880
|
+
}
|
|
626
881
|
/**
|
|
627
882
|
* Perform an `act`-band action: file a note (note kind) or append a feed entry (feed kind). `ctx` is
|
|
628
883
|
* the destination context — normally the active context, but for a G1 auto-routed note it is the
|
|
@@ -644,7 +899,7 @@ export class Scheduler {
|
|
|
644
899
|
const text = routedFrom
|
|
645
900
|
? `Routed a note to ${ctx.name || ctx.id} → ${ws.name}: ${a.summary}`
|
|
646
901
|
: `Filed a note → ${ws.name}: ${a.summary}`;
|
|
647
|
-
await this.appendFeed(owner, { kind: 'act', contextId: ctx.id, contextName: ctx.name || '', text });
|
|
902
|
+
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
903
|
return [key, 'secretary.feed'];
|
|
649
904
|
}
|
|
650
905
|
}
|
|
@@ -658,6 +913,69 @@ export class Scheduler {
|
|
|
658
913
|
const map = rec?.value?.map;
|
|
659
914
|
return (map && typeof map === 'object') ? map : {};
|
|
660
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Learning loop (B): when a CLUSTER of reviewed decisions in this context scored poorly, ask the model
|
|
918
|
+
* for ONE durable operating rule that would avoid repeating them, and post it as a GATED inbox decision
|
|
919
|
+
* card (type 'brain-rule'). The owner approves it in the inbox (applyDecision appends it to the brain) —
|
|
920
|
+
* it is NEVER auto-applied. The source decisions are flagged `proposalUsed` so the same cluster doesn't
|
|
921
|
+
* re-propose every tick. Returns the pending card (+ the keys written) or null when there isn't enough
|
|
922
|
+
* signal / a brain-rule proposal is already pending / the model returns nothing. One paid morsel on propose.
|
|
923
|
+
*/
|
|
924
|
+
async proposeLearnedRule(owner, ownerName, active, existingPending) {
|
|
925
|
+
// One brain-rule proposal at a time — don't stack cards.
|
|
926
|
+
if (Object.values(existingPending).some((p) => p?.type === 'brain-rule'))
|
|
927
|
+
return null;
|
|
928
|
+
const recs = await this.storage.listMemory(owner, { prefix: 'secretary.decision.' });
|
|
929
|
+
const byId = new Map();
|
|
930
|
+
const values = [];
|
|
931
|
+
for (const r of recs) {
|
|
932
|
+
const d = (r.value ?? {});
|
|
933
|
+
if (d.id)
|
|
934
|
+
byId.set(String(d.id), r);
|
|
935
|
+
values.push(d);
|
|
936
|
+
}
|
|
937
|
+
const cluster = poorDecisionCluster(values, active.id);
|
|
938
|
+
if (!cluster.length)
|
|
939
|
+
return null;
|
|
940
|
+
const secretaryGaii = `secretary#${ownerName}@${this.config.nodeId}`;
|
|
941
|
+
const agent = await this.storage.getAgent(secretaryGaii);
|
|
942
|
+
if (!agent)
|
|
943
|
+
return null;
|
|
944
|
+
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"}.`;
|
|
945
|
+
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.`;
|
|
946
|
+
let rule;
|
|
947
|
+
try {
|
|
948
|
+
const out = await completeForOwner(this.storage, this.config, owner, { prompt, systemPrompt, appId: 'schedule:learn:rule' });
|
|
949
|
+
rule = String(extractJsonObject(out.content)?.rule ?? '').trim().slice(0, 400);
|
|
950
|
+
}
|
|
951
|
+
catch {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
if (!rule)
|
|
955
|
+
return null;
|
|
956
|
+
// Flag the source decisions so the cluster doesn't re-propose every tick.
|
|
957
|
+
const now = new Date().toISOString();
|
|
958
|
+
const writes = [];
|
|
959
|
+
for (const d of cluster) {
|
|
960
|
+
const rec = byId.get(String(d.id));
|
|
961
|
+
if (!rec)
|
|
962
|
+
continue;
|
|
963
|
+
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 });
|
|
964
|
+
writes.push(rec.key);
|
|
965
|
+
}
|
|
966
|
+
const promptId = 'learn-' + randomUUID().slice(0, 8);
|
|
967
|
+
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}"`;
|
|
968
|
+
const pending = { type: 'brain-rule', rule, contextId: active.id, contextName: active.name || '', basisCount: cluster.length, question, createdAt: now };
|
|
969
|
+
const record = {
|
|
970
|
+
id: randomUUID(), agentGaii: secretaryGaii, threadId: randomUUID(), direction: 'outbound', senderGaii: owner,
|
|
971
|
+
content: `Proposed operating rule: ${rule}`, status: 'delivered',
|
|
972
|
+
metadata: { prompt: { promptId, question, options: ['Yes, add it', 'No'], allowOther: false } }, createdAt: now,
|
|
973
|
+
};
|
|
974
|
+
await this.storage.createMessage(record);
|
|
975
|
+
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.` });
|
|
976
|
+
writes.push('secretary.feed');
|
|
977
|
+
return { id: promptId, pending, writes };
|
|
978
|
+
}
|
|
661
979
|
/**
|
|
662
980
|
* Post a draft/ask-band action as an inbox decision card (reusing the Phase-3b prompt rails) and
|
|
663
981
|
* return the stashed pending action keyed by promptId. Note actions reuse the existing `file-note`
|
|
@@ -699,31 +1017,69 @@ export class Scheduler {
|
|
|
699
1017
|
await this.storage.createMessage(record);
|
|
700
1018
|
return { id: promptId, pending };
|
|
701
1019
|
}
|
|
702
|
-
/**
|
|
703
|
-
|
|
1020
|
+
/**
|
|
1021
|
+
* Read-modify-write secretary.config to persist the day's spend ledger + any new pending decisions,
|
|
1022
|
+
* plus (B5) routine step completions the tick performed and the action-items it derived. Applied to a
|
|
1023
|
+
* FRESH read (targeted by routine/step id) to avoid clobbering concurrent view edits.
|
|
1024
|
+
*/
|
|
1025
|
+
async persistTickState(owner, contextId, today, morsels, newPending, stepCompletions = [], newActionItems = [], rearmedRoutines = []) {
|
|
704
1026
|
const rec = await this.storage.getMemory(owner, 'secretary.config');
|
|
705
1027
|
const cfg = (rec?.value ?? {});
|
|
706
1028
|
const ledger = morsels > 0 ? bumpLedger(cfg.autonomousLedger, contextId, today, morsels) : cfg.autonomousLedger;
|
|
707
1029
|
const pending = Object.keys(newPending).length ? { ...(cfg.pendingDecisions ?? {}), ...newPending } : cfg.pendingDecisions;
|
|
708
|
-
const next = { ...cfg, autonomousLedger: ledger, pendingDecisions: pending };
|
|
709
1030
|
const now = new Date().toISOString();
|
|
1031
|
+
// B5/G2: apply routine re-arms + step completions + merge action-items; G8: scrub quick actions.
|
|
1032
|
+
let contexts = cfg.contexts;
|
|
1033
|
+
if (Array.isArray(cfg.contexts)) {
|
|
1034
|
+
contexts = cfg.contexts.map((c) => {
|
|
1035
|
+
if (c.id !== contextId)
|
|
1036
|
+
return c;
|
|
1037
|
+
// G8 defense-in-depth: drop any disallowed (e.g. smuggled 'run') quick action server-side.
|
|
1038
|
+
const quickActions = Array.isArray(c.quickActions) ? c.quickActions.filter(isAllowedQuickAction) : c.quickActions;
|
|
1039
|
+
let routines = c.routines;
|
|
1040
|
+
// G2: replace re-armed routines first (steps reset to pending) so a same-tick file-step
|
|
1041
|
+
// completion below lands on the freshly-armed step.
|
|
1042
|
+
if (rearmedRoutines.length && Array.isArray(routines)) {
|
|
1043
|
+
routines = routines.map((r) => {
|
|
1044
|
+
const armed = rearmedRoutines.find((x) => x.id === r.id);
|
|
1045
|
+
return armed ? armed.routine : r;
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
if (stepCompletions.length && Array.isArray(routines)) {
|
|
1049
|
+
routines = routines.map((r) => {
|
|
1050
|
+
const comps = stepCompletions.filter((sc) => sc.routineId === r.id);
|
|
1051
|
+
if (!comps.length)
|
|
1052
|
+
return r;
|
|
1053
|
+
const steps = (r.steps || []).map((s) => {
|
|
1054
|
+
const comp = comps.find((x) => x.stepId === s.id);
|
|
1055
|
+
return comp ? { ...s, status: comp.status, result: { summary: comp.summary, ts: now } } : s;
|
|
1056
|
+
});
|
|
1057
|
+
// B4 fix: a 'delegated' step is NOT settled (its task is still out) — the routine stays active.
|
|
1058
|
+
const settled = steps.every((s) => s.status !== 'pending' && s.status !== 'running' && s.status !== 'delegated');
|
|
1059
|
+
const results = [...comps.map((x) => ({ ts: now, summary: x.summary })), ...(r.results || [])].slice(0, 20);
|
|
1060
|
+
return { ...r, steps, results, lastRunAt: now, status: settled ? 'done' : r.status };
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
let actionItems = c.actionItems;
|
|
1064
|
+
if (newActionItems.length) {
|
|
1065
|
+
const existing = Array.isArray(c.actionItems) ? c.actionItems : [];
|
|
1066
|
+
const seenOpen = new Set(existing.filter((a) => a.status === 'open').map((a) => actionItemKey(a)));
|
|
1067
|
+
const fresh = newActionItems.filter((a) => !seenOpen.has(actionItemKey(a)));
|
|
1068
|
+
actionItems = [...fresh, ...existing].slice(0, 50);
|
|
1069
|
+
}
|
|
1070
|
+
return { ...c, routines, actionItems, quickActions };
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const next = { ...cfg, contexts, autonomousLedger: ledger, pendingDecisions: pending };
|
|
710
1074
|
await this.storage.setMemory({
|
|
711
1075
|
key: 'secretary.config', ownerGaii: owner, value: next, visibility: 'private', tags: ['secretary', 'config'],
|
|
712
1076
|
ttlHours: null, version: rec ? rec.version + 1 : 1, createdAt: rec?.createdAt ?? now, updatedAt: now,
|
|
713
1077
|
});
|
|
714
1078
|
}
|
|
715
|
-
/** Append one entry to the owner's Home feed (`secretary.feed`, newest first, capped
|
|
1079
|
+
/** Append one entry to the owner's Home feed (`secretary.feed`, newest first, capped). Delegates to the
|
|
1080
|
+
* shared helper so the live DM responder / clarify-produce write the SAME feed. */
|
|
716
1081
|
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
|
-
});
|
|
1082
|
+
await appendSecretaryFeed(this.storage, owner, entry);
|
|
727
1083
|
}
|
|
728
1084
|
/**
|
|
729
1085
|
* Learning-loop review sweep (Phase 5): find open decision-log contracts whose `revisitWhen` has
|
|
@@ -766,6 +1122,53 @@ export class Scheduler {
|
|
|
766
1122
|
}
|
|
767
1123
|
return done;
|
|
768
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Carry out a fired trigger's BOUND action (if any). Returns null for a plain reminder (the caller then
|
|
1127
|
+
* posts a follow-up action-item); otherwise `{ note, morsels }` describing what ran (the caller folds the
|
|
1128
|
+
* note into the feed entry and meters the morsels). Two bindings:
|
|
1129
|
+
* - workflow: start the named Agent Workflow run via the deterministic engine.
|
|
1130
|
+
* - specialist: queue a task (the trigger's `prompt`, defaulting to its label) for the named specialist
|
|
1131
|
+
* and run it node-side now (owner key + quality contract), so the deliverable lands this same tick.
|
|
1132
|
+
* Best-effort: any failure (unknown workflow/specialist, engine down, spend-pause) degrades to a plain
|
|
1133
|
+
* reminder so a misconfigured binding never breaks the tick.
|
|
1134
|
+
*/
|
|
1135
|
+
async fireBoundTriggerAction(owner, ownerName, tr, label, specialists, writes) {
|
|
1136
|
+
const act = tr.action || {};
|
|
1137
|
+
try {
|
|
1138
|
+
if (act.kind === 'workflow' && act.workflowId) {
|
|
1139
|
+
const engine = getActiveWorkflowEngine();
|
|
1140
|
+
if (!engine)
|
|
1141
|
+
return null;
|
|
1142
|
+
const r = await engine.startRun(owner, ownerName, String(act.workflowId), { mode: 'full-live' });
|
|
1143
|
+
if ('error' in r) {
|
|
1144
|
+
logger.info('[secretary tick] bound workflow refused to start', { id: tr.id, wf: act.workflowId, err: r.error.join('; ') });
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
writes.push(`workflows.run.${act.workflowId}.${r.runId}`);
|
|
1148
|
+
return { note: `▶️ ${act.workflowId}`, morsels: 1 };
|
|
1149
|
+
}
|
|
1150
|
+
if (act.kind === 'specialist' && act.specialistName) {
|
|
1151
|
+
const spec = specialists.find((s) => s.name === act.specialistName);
|
|
1152
|
+
if (!spec)
|
|
1153
|
+
return null;
|
|
1154
|
+
const now = new Date().toISOString();
|
|
1155
|
+
await this.storage.createAgentTask({
|
|
1156
|
+
id: randomUUID(), agentGaii: spec.gaii, ownerGaii: owner,
|
|
1157
|
+
title: label, description: String(act.prompt || label).slice(0, 4000),
|
|
1158
|
+
scope: [], rules: [], verification: { userExpects: '', technicalChecks: [] }, todos: [],
|
|
1159
|
+
status: 'queued', parentTaskId: `trigger:${tr.id}`, createdAt: now, updatedAt: now,
|
|
1160
|
+
});
|
|
1161
|
+
const specWrites = await runSpecialistQueue(this.storage, this.config, owner, spec, { limit: 1 });
|
|
1162
|
+
writes.push(...specWrites);
|
|
1163
|
+
// 1 morsel for the specialist run even if it deferred (kept the queued task) — the run was attempted.
|
|
1164
|
+
return { note: `🧩 ${spec.name}`, morsels: Math.max(1, specWrites.length) };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
catch (e) {
|
|
1168
|
+
logger.info('[secretary tick] bound trigger action failed', { id: tr.id, kind: act.kind, err: e.message });
|
|
1169
|
+
}
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
769
1172
|
/**
|
|
770
1173
|
* `agent_task` kind: materialise an AgentTaskRecord into the target agent's
|
|
771
1174
|
* queue and wake it via the existing webhook/MCP/SSE fan-out. The schedule is
|