akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/base/symbols.ts +4 -0
- package/client/clientRuntime.ts +8 -0
- package/client/cn.ts +7 -0
- package/client/csrTypes.ts +2 -0
- package/client/frameDebug.ts +3 -2
- package/common/Logger.ts +4 -0
- package/common/deepObjectify.ts +21 -7
- package/common/index.ts +14 -0
- package/common/mcpExposure.ts +99 -0
- package/common/pathSet.ts +17 -5
- package/common/routeConvention.ts +30 -0
- package/constant/crystalize.ts +3 -1
- package/constant/fieldInfo.ts +6 -0
- package/constant/getDefault.ts +37 -9
- package/constant/immerify.ts +1 -1
- package/constant/index.ts +2 -0
- package/constant/labelOf.ts +17 -0
- package/constant/mask.ts +60 -0
- package/constant/purify.ts +17 -11
- package/constant/types.ts +14 -12
- package/dictionary/DictionaryLookup.ts +30 -0
- package/dictionary/agent.dictionary.ts +31 -0
- package/dictionary/agentTurn.dictionary.ts +29 -0
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/dictInfo.ts +8 -0
- package/dictionary/dictionary.ts +9 -3
- package/dictionary/index.ts +1 -0
- package/document/database.ts +21 -0
- package/document/filterMeta.ts +7 -0
- package/document/index.ts +1 -0
- package/document/into.ts +7 -1
- package/document/noDocumentError.ts +12 -0
- package/fetch/agentTurn.ts +19 -0
- package/fetch/client/fetchClient.ts +71 -13
- package/fetch/client/wsClient.ts +17 -2
- package/fetch/fetchType/endpointFetch.type.ts +10 -4
- package/fetch/index.ts +1 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
- package/package.json +11 -9
- package/server/akanApp.ts +30 -34
- package/server/akanOption.ts +65 -7
- package/server/akanServer.ts +172 -7
- package/server/animatedImage.ts +83 -0
- package/server/artifact/routeClientCache.ts +14 -0
- package/server/assetEncoding.ts +47 -0
- package/server/devtools/signalSerializer.ts +2 -2
- package/server/devtools/types.ts +2 -2
- package/server/di/diLifecycle.ts +31 -3
- package/server/di/predefinedAdaptor.ts +6 -0
- package/server/imageOptimizer.ts +33 -46
- package/server/index.ts +1 -2
- package/server/mcp/McpAuth.ts +177 -0
- package/server/mcp/McpDispatcher.ts +245 -0
- package/server/mcp/McpEventStream.ts +76 -0
- package/server/mcp/McpExecutionContext.ts +105 -0
- package/server/mcp/McpRouter.ts +591 -0
- package/server/mcp/index.ts +5 -0
- package/server/resolver/CascadeRunner.ts +1 -1
- package/server/resolver/database.resolver.ts +42 -5
- package/server/resolver/service.resolver.ts +29 -0
- package/server/resolver/signal.resolver.ts +16 -1
- package/server/routeTreeBuilder.ts +2 -44
- package/server/rscWorker.tsx +6 -2
- package/server/subRouteIndexDocument.tsx +61 -0
- package/server/systemPageDocument.tsx +2 -2
- package/server/systemPages.tsx +49 -16
- package/server/vendor/akanjs-fetch.ts +1 -0
- package/server/webRouter.ts +159 -35
- package/service/agent.service.ts +14 -0
- package/service/base.service.ts +2 -1
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/database.adaptor.ts +78 -24
- package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
- package/service/predefinedAdaptor/index.ts +3 -0
- package/service/predefinedAdaptor/insightQuery.ts +183 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
- package/service/predefinedAdaptor/role.adaptor.ts +2 -0
- package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
- package/service/serviceModule.ts +17 -0
- package/service/types.ts +4 -0
- package/signal/agent/AgentCatalogue.ts +165 -0
- package/signal/agent/index.ts +1 -0
- package/signal/agent.signal.ts +42 -0
- package/signal/agentTurn.ts +8 -0
- package/signal/agentTurnStream.ts +48 -0
- package/signal/base.signal.ts +2 -1
- package/signal/endpointInfo.ts +38 -2
- package/signal/guard.ts +13 -1
- package/signal/guards.ts +52 -1
- package/signal/index.ts +6 -0
- package/signal/mcp/McpDocument.ts +286 -0
- package/signal/mcp/McpProgress.ts +106 -0
- package/signal/mcp/McpUriTemplate.ts +85 -0
- package/signal/mcp/Msg.ts +368 -0
- package/signal/mcp/index.ts +5 -0
- package/signal/mcp/mcpProtocol.ts +120 -0
- package/signal/middleware.ts +7 -3
- package/signal/openapi/openapi.ts +32 -171
- package/signal/schema/JsonSchemaBuilder.ts +211 -0
- package/signal/schema/index.ts +1 -0
- package/signal/signalContext.ts +85 -9
- package/signal/slice.ts +9 -9
- package/signal/types.ts +1 -1
- package/store/action.ts +11 -22
- package/store/actionTag.ts +33 -0
- package/store/agent/AgentBridge.ts +323 -0
- package/store/agent/AgentContext.ts +74 -0
- package/store/agent/AgentPrompts.ts +82 -0
- package/store/agent/AgentVisibility.ts +68 -0
- package/store/agent/ScreenReader.ts +208 -0
- package/store/agent/StoreCatalogue.ts +351 -0
- package/store/agent/StoreSurfaceSource.ts +122 -0
- package/store/agent/index.ts +9 -0
- package/store/agent/storeSurface.ts +28 -0
- package/store/agent/types.ts +50 -0
- package/store/agentic/StToolBuilder.ts +199 -0
- package/store/agentic/attachAgentic.ts +22 -0
- package/store/agentic/index.ts +5 -0
- package/store/agentic/readableValue.ts +17 -0
- package/store/agentic/useStExpose.ts +18 -0
- package/store/agentic/useStState.ts +35 -0
- package/store/baseSt.ts +7 -0
- package/store/databaseStateNames.ts +31 -0
- package/store/formSetterNames.ts +21 -0
- package/store/index.ts +8 -0
- package/store/rootStore.ts +3 -1
- package/store/sliceRole.ts +36 -0
- package/store/state.ts +2 -12
- package/store/store.ts +19 -1
- package/store/storeInstance.ts +165 -20
- package/store/storeRegistry.ts +16 -1
- package/store/types.ts +18 -0
- package/store/withSelector.ts +7 -8
- package/test/registerDom.ts +4 -0
- package/types/base/symbols.d.ts +4 -0
- package/types/client/cn.d.ts +5 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/Logger.d.ts +2 -0
- package/types/common/deepObjectify.d.ts +4 -2
- package/types/common/index.d.ts +2 -1
- package/types/common/mcpExposure.d.ts +60 -0
- package/types/common/routeConvention.d.ts +8 -0
- package/types/constant/fieldInfo.d.ts +1 -0
- package/types/constant/getDefault.d.ts +5 -0
- package/types/constant/index.d.ts +2 -0
- package/types/constant/labelOf.d.ts +6 -0
- package/types/constant/mask.d.ts +34 -0
- package/types/constant/purify.d.ts +2 -2
- package/types/constant/types.d.ts +2 -2
- package/types/dictionary/DictionaryLookup.d.ts +14 -0
- package/types/dictionary/agent.dictionary.d.ts +1 -0
- package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictInfo.d.ts +6 -0
- package/types/dictionary/dictionary.d.ts +11 -9
- package/types/dictionary/index.d.ts +1 -0
- package/types/document/database.d.ts +20 -0
- package/types/document/filterMeta.d.ts +1 -0
- package/types/document/index.d.ts +1 -0
- package/types/document/into.d.ts +6 -1
- package/types/document/noDocumentError.d.ts +12 -0
- package/types/fetch/agentTurn.d.ts +593 -0
- package/types/fetch/client/fetchClient.d.ts +10 -0
- package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
- package/types/fetch/index.d.ts +1 -0
- package/types/server/akanOption.d.ts +35 -7
- package/types/server/akanServer.d.ts +44 -1
- package/types/server/animatedImage.d.ts +7 -0
- package/types/server/artifact/routeClientCache.d.ts +6 -0
- package/types/server/assetEncoding.d.ts +7 -0
- package/types/server/devtools/types.d.ts +2 -2
- package/types/server/di/predefinedAdaptor.d.ts +4 -1
- package/types/server/index.d.ts +0 -2
- package/types/server/mcp/McpAuth.d.ts +62 -0
- package/types/server/mcp/McpDispatcher.d.ts +54 -0
- package/types/server/mcp/McpEventStream.d.ts +18 -0
- package/types/server/mcp/McpExecutionContext.d.ts +44 -0
- package/types/server/mcp/McpRouter.d.ts +60 -0
- package/types/server/mcp/index.d.ts +5 -0
- package/types/server/mcp.d.ts +1 -0
- package/types/server/subRouteIndexDocument.d.ts +8 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/systemPages.d.ts +5 -0
- package/types/server/vendor/akanjs-fetch.d.ts +1 -0
- package/types/service/agent.service.d.ts +12 -0
- package/types/service/base.service.d.ts +4 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
- package/types/service/predefinedAdaptor/index.d.ts +3 -0
- package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
- package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
- package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
- package/types/service/types.d.ts +4 -1
- package/types/signal/agent/AgentCatalogue.d.ts +100 -0
- package/types/signal/agent/index.d.ts +1 -0
- package/types/signal/agent.d.ts +1 -0
- package/types/signal/agent.signal.d.ts +29 -0
- package/types/signal/agentTurn.d.ts +9 -0
- package/types/signal/agentTurnStream.d.ts +16 -0
- package/types/signal/base.signal.d.ts +1 -1
- package/types/signal/endpointInfo.d.ts +21 -3
- package/types/signal/guard.d.ts +11 -0
- package/types/signal/guards.d.ts +22 -1
- package/types/signal/index.d.ts +6 -0
- package/types/signal/mcp/McpDocument.d.ts +65 -0
- package/types/signal/mcp/McpProgress.d.ts +40 -0
- package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
- package/types/signal/mcp/Msg.d.ts +143 -0
- package/types/signal/mcp/index.d.ts +5 -0
- package/types/signal/mcp/mcpProtocol.d.ts +107 -0
- package/types/signal/mcp.d.ts +1 -0
- package/types/signal/openapi/openapi.d.ts +3 -3
- package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
- package/types/signal/schema/index.d.ts +1 -0
- package/types/signal/schema.d.ts +1 -0
- package/types/signal/signalContext.d.ts +16 -1
- package/types/signal/slice.d.ts +8 -8
- package/types/signal/types.d.ts +1 -1
- package/types/store/actionTag.d.ts +21 -0
- package/types/store/agent/AgentBridge.d.ts +77 -0
- package/types/store/agent/AgentContext.d.ts +16 -0
- package/types/store/agent/AgentPrompts.d.ts +31 -0
- package/types/store/agent/AgentVisibility.d.ts +21 -0
- package/types/store/agent/ScreenReader.d.ts +11 -0
- package/types/store/agent/StoreCatalogue.d.ts +26 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
- package/types/store/agent/index.d.ts +9 -0
- package/types/store/agent/storeSurface.d.ts +14 -0
- package/types/store/agent/types.d.ts +49 -0
- package/types/store/agent.d.ts +1 -0
- package/types/store/agentic/StToolBuilder.d.ts +41 -0
- package/types/store/agentic/attachAgentic.d.ts +14 -0
- package/types/store/agentic/index.d.ts +5 -0
- package/types/store/agentic/readableValue.d.ts +3 -0
- package/types/store/agentic/useStExpose.d.ts +8 -0
- package/types/store/agentic/useStState.d.ts +12 -0
- package/types/store/agentic.d.ts +1 -0
- package/types/store/baseSt.d.ts +29 -22
- package/types/store/databaseStateNames.d.ts +25 -0
- package/types/store/formSetterNames.d.ts +16 -0
- package/types/store/index.d.ts +7 -0
- package/types/store/rootStore.d.ts +5 -1
- package/types/store/sliceRole.d.ts +25 -0
- package/types/store/store.d.ts +7 -2
- package/types/store/storeInstance.d.ts +33 -1
- package/types/store/storeRegistry.d.ts +5 -0
- package/types/store/types.d.ts +15 -0
- package/types/store/withSelector.d.ts +7 -8
- package/types/test/registerDom.d.ts +1 -0
- package/types/ui/Agent/Approval.d.ts +7 -0
- package/types/ui/Agent/Bubble.d.ts +13 -0
- package/types/ui/Agent/Chat.d.ts +24 -0
- package/types/ui/Agent/Context.d.ts +9 -0
- package/types/ui/Agent/Dock.d.ts +16 -0
- package/types/ui/Agent/Guide.d.ts +9 -0
- package/types/ui/Agent/Section.d.ts +10 -0
- package/types/ui/Agent/StateKey.d.ts +16 -0
- package/types/ui/Agent/Tool.d.ts +15 -0
- package/types/ui/Agent/Transcript.d.ts +13 -0
- package/types/ui/Agent/Zone.d.ts +23 -0
- package/types/ui/Agent/fetchRunner.d.ts +12 -0
- package/types/ui/Agent/index.d.ts +17 -0
- package/types/ui/Agent/index_.d.ts +1 -0
- package/types/ui/Agent/sessionHistory.d.ts +12 -0
- package/types/ui/Agent.d.ts +1 -0
- package/types/ui/Badge.d.ts +1 -1
- package/types/ui/Button.d.ts +1 -1
- package/types/ui/CsrImage.d.ts +1 -1
- package/types/ui/Dropdown.d.ts +2 -0
- package/types/ui/Image.d.ts +3 -3
- package/types/ui/Layout/BottomInset.d.ts +2 -1
- package/types/ui/Layout/index.d.ts +1 -1
- package/types/ui/Signal/style.d.ts +4 -1
- package/types/ui/UiOverride/context.d.ts +14 -2
- package/types/ui/agentAttrs.d.ts +14 -0
- package/types/ui/index.d.ts +4 -1
- package/types/ui/overlayLayer.d.ts +24 -0
- package/types/ui/recipe/badgeRecipe.d.ts +7 -3
- package/types/ui/recipe/buttonRecipe.d.ts +7 -3
- package/types/ui/recipe/inputRecipe.d.ts +5 -1
- package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
- package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
- package/types/vendor/use-agentic/Agentic.d.ts +21 -0
- package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
- package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
- package/types/vendor/use-agentic/index.d.ts +13 -0
- package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
- package/types/vendor/use-agentic/types.d.ts +133 -0
- package/types/vendor/use-agentic/useAgent.d.ts +4 -0
- package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
- package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
- package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
- package/types/vendor/use-agentic.d.ts +1 -0
- package/types/webkit/index.d.ts +3 -0
- package/types/webkit/lazy.d.ts +12 -0
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/types/webkit/useEscapeKey.d.ts +5 -0
- package/types/webkit/useFrameRuntime.d.ts +6 -1
- package/types/webkit/useScreenScope.d.ts +18 -0
- package/ui/Agent/Approval.tsx +29 -0
- package/ui/Agent/Bubble.tsx +74 -0
- package/ui/Agent/Chat.tsx +225 -0
- package/ui/Agent/Context.tsx +38 -0
- package/ui/Agent/Dock.tsx +72 -0
- package/ui/Agent/Guide.tsx +16 -0
- package/ui/Agent/Section.tsx +24 -0
- package/ui/Agent/StateKey.tsx +44 -0
- package/ui/Agent/Tool.tsx +66 -0
- package/ui/Agent/Transcript.tsx +33 -0
- package/ui/Agent/Zone.tsx +48 -0
- package/ui/Agent/fetchRunner.ts +30 -0
- package/ui/Agent/index.ts +12 -0
- package/ui/Agent/index_.tsx +4 -0
- package/ui/Agent/sessionHistory.ts +33 -0
- package/ui/Badge.tsx +3 -3
- package/ui/BottomSheet.tsx +5 -0
- package/ui/Button.tsx +6 -2
- package/ui/Constant/Doc.tsx +1 -1
- package/ui/CsrImage.tsx +1 -1
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/DatePicker.tsx +5 -4
- package/ui/Dialog/Modal.tsx +12 -15
- package/ui/DraggableList.tsx +3 -1
- package/ui/Dropdown.tsx +33 -11
- package/ui/Field.tsx +30 -16
- package/ui/Image.tsx +3 -3
- package/ui/Input.tsx +14 -5
- package/ui/Layout/BottomInset.tsx +6 -1
- package/ui/Load/Units.tsx +11 -2
- package/ui/Load/View.tsx +7 -2
- package/ui/Loading/ProgressBar.tsx +8 -1
- package/ui/Menu.tsx +7 -8
- package/ui/Model/EditModal.tsx +37 -2
- package/ui/Model/SureToRemove.tsx +2 -1
- package/ui/Model/index_.tsx +42 -15
- package/ui/ObjectId.tsx +3 -4
- package/ui/Pagination.tsx +3 -4
- package/ui/Popconfirm.tsx +8 -7
- package/ui/Select.tsx +6 -4
- package/ui/Signal/RestApi.tsx +43 -16
- package/ui/Signal/WebSocket.tsx +1 -1
- package/ui/Signal/style.ts +6 -1
- package/ui/Switch.tsx +2 -0
- package/ui/System/CSR.tsx +6 -2
- package/ui/System/ThemeToggle.tsx +55 -33
- package/ui/ToggleSelect.tsx +4 -3
- package/ui/Tooltip.tsx +2 -1
- package/ui/UiOverride/context.ts +14 -2
- package/ui/agentAttrs.ts +19 -0
- package/ui/index.ts +10 -1
- package/ui/overlayLayer.ts +39 -0
- package/ui/recipe/badgeRecipe.ts +22 -3
- package/ui/recipe/buttonRecipe.ts +51 -2
- package/ui/recipe/factory.ts +2 -2
- package/ui/recipe/inputRecipe.ts +18 -4
- package/vendor/use-agentic/AgentProvider.tsx +39 -0
- package/vendor/use-agentic/AgentScope.tsx +20 -0
- package/vendor/use-agentic/AgentSession.ts +292 -0
- package/vendor/use-agentic/Agentic.tsx +44 -0
- package/vendor/use-agentic/AgenticSurface.ts +294 -0
- package/vendor/use-agentic/WIRE.md +57 -0
- package/vendor/use-agentic/httpRunner.ts +115 -0
- package/vendor/use-agentic/index.ts +14 -0
- package/vendor/use-agentic/surfaceContext.ts +9 -0
- package/vendor/use-agentic/types.ts +144 -0
- package/vendor/use-agentic/useAgent.ts +17 -0
- package/vendor/use-agentic/useAgentGuide.ts +16 -0
- package/vendor/use-agentic/useAgentResource.ts +30 -0
- package/vendor/use-agentic/useAgentState.ts +61 -0
- package/vendor/use-agentic/useAgentTool.ts +55 -0
- package/webkit/bootCsr.tsx +2 -33
- package/webkit/index.ts +3 -0
- package/webkit/lazy.tsx +27 -3
- package/webkit/useCsrValues.ts +121 -4
- package/webkit/useEscapeKey.tsx +42 -0
- package/webkit/useFrameRuntime.ts +65 -32
- package/webkit/useScreenScope.tsx +49 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { STATE_META } from "akanjs/base";
|
|
2
|
+
import { StoreRegistry } from "../storeRegistry";
|
|
3
|
+
import type { StoreAgentExposure } from "../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Which stores the agent surface may show, derived from the registry: every registered store's state keys name
|
|
7
|
+
* their owner, and a store's `static agent` declaration is its own exposure decision. Liveness rides on top —
|
|
8
|
+
* `liveOwners` maps the subscribed keys back to store refNames, so the published surface follows the rendered
|
|
9
|
+
* screen instead of the whole bundle.
|
|
10
|
+
*/
|
|
11
|
+
export class AgentVisibility {
|
|
12
|
+
#owners: Map<string, string> | null = null;
|
|
13
|
+
#ownersSize = -1;
|
|
14
|
+
|
|
15
|
+
/** State key → owning store refName. First registration wins where a lib mixin shares a key. */
|
|
16
|
+
#stateOwners(): Map<string, string> {
|
|
17
|
+
const stores = StoreRegistry.stores;
|
|
18
|
+
if (this.#owners && this.#ownersSize === stores.size) return this.#owners;
|
|
19
|
+
const owners = new Map<string, string>();
|
|
20
|
+
for (const [refName, cls] of stores)
|
|
21
|
+
for (const key of Object.keys(cls[STATE_META] ?? {})) if (!owners.has(key)) owners.set(key, refName);
|
|
22
|
+
this.#owners = owners;
|
|
23
|
+
this.#ownersSize = stores.size;
|
|
24
|
+
return owners;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
stateOwner(key: string): string | undefined {
|
|
28
|
+
return this.#stateOwners().get(key);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The stores that declared an exposure of their own, for the catalogue's refusal lines. */
|
|
32
|
+
declaredExposures(): Map<string, StoreAgentExposure> {
|
|
33
|
+
const declared = new Map<string, StoreAgentExposure>();
|
|
34
|
+
for (const [refName, cls] of StoreRegistry.stores) if (cls.agent !== undefined) declared.set(refName, cls.agent);
|
|
35
|
+
return declared;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#exposure(refName: string | undefined): StoreAgentExposure | undefined {
|
|
39
|
+
return refName ? StoreRegistry.get(refName)?.agent : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
visibleKey(key: string): boolean {
|
|
43
|
+
const exposure = this.#exposure(this.stateOwner(key));
|
|
44
|
+
if (exposure === false) return false;
|
|
45
|
+
return !exposure?.exclude.includes(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
visibleAction(name: string, ownerRefName?: string): boolean {
|
|
49
|
+
const exposure = this.#exposure(ownerRefName);
|
|
50
|
+
if (exposure === false) return false;
|
|
51
|
+
return !exposure?.exclude.includes(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The stores the rendered screen is reading right now: owners of the subscribed, still-visible keys. A key no
|
|
56
|
+
* registered store owns has no module to activate, and an action with no owner stays published — only fabricated
|
|
57
|
+
* stores outside the registry produce either.
|
|
58
|
+
*/
|
|
59
|
+
liveOwners(liveKeys: ReadonlyMap<string, number>): Set<string> {
|
|
60
|
+
const owners = new Set<string>();
|
|
61
|
+
for (const key of liveKeys.keys()) {
|
|
62
|
+
if (!this.visibleKey(key)) continue;
|
|
63
|
+
const owner = this.stateOwner(key);
|
|
64
|
+
if (owner) owners.add(owner);
|
|
65
|
+
}
|
|
66
|
+
return owners;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const skipTags = new Set([
|
|
2
|
+
"SCRIPT",
|
|
3
|
+
"STYLE",
|
|
4
|
+
"NOSCRIPT",
|
|
5
|
+
"TEMPLATE",
|
|
6
|
+
"IFRAME",
|
|
7
|
+
"SVG",
|
|
8
|
+
"CANVAS",
|
|
9
|
+
"VIDEO",
|
|
10
|
+
"AUDIO",
|
|
11
|
+
"OBJECT",
|
|
12
|
+
"EMBED",
|
|
13
|
+
]);
|
|
14
|
+
const headingLevels = { H1: 1, H2: 2, H3: 3, H4: 4, H5: 5, H6: 6 } as const;
|
|
15
|
+
const blockTags = new Set([
|
|
16
|
+
"ADDRESS",
|
|
17
|
+
"ARTICLE",
|
|
18
|
+
"ASIDE",
|
|
19
|
+
"BLOCKQUOTE",
|
|
20
|
+
"DD",
|
|
21
|
+
"DETAILS",
|
|
22
|
+
"DIV",
|
|
23
|
+
"DL",
|
|
24
|
+
"DT",
|
|
25
|
+
"FIELDSET",
|
|
26
|
+
"FIGCAPTION",
|
|
27
|
+
"FIGURE",
|
|
28
|
+
"FOOTER",
|
|
29
|
+
"FORM",
|
|
30
|
+
"HEADER",
|
|
31
|
+
"HR",
|
|
32
|
+
"LEGEND",
|
|
33
|
+
"MAIN",
|
|
34
|
+
"NAV",
|
|
35
|
+
"OL",
|
|
36
|
+
"P",
|
|
37
|
+
"SECTION",
|
|
38
|
+
"SUMMARY",
|
|
39
|
+
"TABLE",
|
|
40
|
+
"THEAD",
|
|
41
|
+
"TBODY",
|
|
42
|
+
"TFOOT",
|
|
43
|
+
"TR",
|
|
44
|
+
"UL",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Serializes what the page is rendering into compact text an agent can answer questions from: headings keep their
|
|
49
|
+
* level, links keep their href, controls keep their value and `data-akan-*` annotation. The agent's own UI is
|
|
50
|
+
* marked `data-agent-ui` and skipped, so a turn never re-reads its own transcript, and a password value is never
|
|
51
|
+
* read — the screen shows dots, so the DOM holds more than the user sees.
|
|
52
|
+
*/
|
|
53
|
+
export class ScreenReader {
|
|
54
|
+
static readonly limit = 8000;
|
|
55
|
+
|
|
56
|
+
static read(root?: HTMLElement | null): string {
|
|
57
|
+
if (typeof document === "undefined") return "No rendered document is available.";
|
|
58
|
+
const reader = new ScreenReader();
|
|
59
|
+
const title = document.title.trim();
|
|
60
|
+
if (title) reader.#lines.push(`Page: ${title}`);
|
|
61
|
+
reader.#walk(root ?? document.body);
|
|
62
|
+
reader.#flush();
|
|
63
|
+
const text = reader.#lines.join("\n").trim();
|
|
64
|
+
if (text.length <= ScreenReader.limit) return text || "The page is rendering nothing readable.";
|
|
65
|
+
return `${text.slice(0, ScreenReader.limit)}… [truncated ${text.length - ScreenReader.limit} more characters]`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#lines: string[] = [];
|
|
69
|
+
#buffer = "";
|
|
70
|
+
#length = 0;
|
|
71
|
+
|
|
72
|
+
#walk(node: Node): void {
|
|
73
|
+
if (this.#length > ScreenReader.limit * 2) return;
|
|
74
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
75
|
+
const text = (node.textContent ?? "").replace(/\s+/g, " ");
|
|
76
|
+
if (text.trim()) this.#buffer += text;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
80
|
+
const el = node as HTMLElement;
|
|
81
|
+
const tag = el.tagName.toUpperCase();
|
|
82
|
+
if (skipTags.has(tag)) return;
|
|
83
|
+
if (el.hasAttribute("data-agent-ui") || el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true")
|
|
84
|
+
return;
|
|
85
|
+
if (typeof el.checkVisibility === "function" && !el.checkVisibility()) return;
|
|
86
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
|
|
87
|
+
this.#control(el, tag);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (tag === "IMG") {
|
|
91
|
+
const alt = el.getAttribute("alt");
|
|
92
|
+
if (alt) this.#buffer += ` [image: ${alt}]`;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (tag === "BR") {
|
|
96
|
+
this.#flush();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (tag === "PRE") {
|
|
100
|
+
this.#pre(el);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (tag === "A") {
|
|
104
|
+
this.#anchor(el);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (tag === "BUTTON" || el.getAttribute("role") === "button") {
|
|
108
|
+
this.#button(el);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const level = headingLevels[tag as keyof typeof headingLevels];
|
|
112
|
+
if (level) {
|
|
113
|
+
this.#flush();
|
|
114
|
+
this.#walkChildren(el);
|
|
115
|
+
this.#flush(`${"#".repeat(level)} `);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (tag === "LI") {
|
|
119
|
+
this.#flush();
|
|
120
|
+
this.#walkChildren(el);
|
|
121
|
+
this.#flush("- ");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (tag === "TD" || tag === "TH") {
|
|
125
|
+
if (this.#buffer.trim()) this.#buffer += " |";
|
|
126
|
+
this.#walkChildren(el);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (blockTags.has(tag)) {
|
|
130
|
+
this.#flush();
|
|
131
|
+
this.#walkChildren(el);
|
|
132
|
+
this.#flush();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.#walkChildren(el);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#walkChildren(el: HTMLElement) {
|
|
139
|
+
for (const child of el.childNodes) this.#walk(child);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#anchor(el: HTMLElement) {
|
|
143
|
+
const href = el.getAttribute("href") ?? "";
|
|
144
|
+
const before = this.#buffer;
|
|
145
|
+
this.#walkChildren(el);
|
|
146
|
+
const text = this.#buffer.slice(before.length).replace(/\s+/g, " ").trim();
|
|
147
|
+
if (href && href !== "#" && !href.startsWith("javascript:") && text !== href) this.#buffer += ` (${href})`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#button(el: HTMLElement) {
|
|
151
|
+
const before = this.#buffer;
|
|
152
|
+
this.#walkChildren(el);
|
|
153
|
+
const inner = this.#buffer.slice(before.length).replace(/\s+/g, " ").trim();
|
|
154
|
+
this.#buffer = before;
|
|
155
|
+
const label = inner || el.getAttribute("aria-label") || "";
|
|
156
|
+
const action = el.getAttribute("data-akan-action");
|
|
157
|
+
if (label || action) this.#buffer += ` [button: ${label}${action ? ` → ${action}` : ""}]`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#control(el: HTMLElement, tag: string) {
|
|
161
|
+
const input = el as HTMLInputElement;
|
|
162
|
+
const type = (el.getAttribute("type") ?? (tag === "INPUT" ? "text" : tag.toLowerCase())).toLowerCase();
|
|
163
|
+
if (type === "hidden") return;
|
|
164
|
+
const name =
|
|
165
|
+
el.getAttribute("data-akan-state") ??
|
|
166
|
+
el.getAttribute("aria-label") ??
|
|
167
|
+
el.getAttribute("placeholder") ??
|
|
168
|
+
el.getAttribute("name") ??
|
|
169
|
+
type;
|
|
170
|
+
if (type === "password") {
|
|
171
|
+
this.#buffer += ` [input ${name}]`;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (type === "checkbox" || type === "radio") {
|
|
175
|
+
this.#buffer += ` [${type} ${name}: ${input.checked ? "on" : "off"}]`;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const raw =
|
|
179
|
+
tag === "SELECT"
|
|
180
|
+
? ((el as unknown as HTMLSelectElement).selectedOptions?.[0]?.textContent ??
|
|
181
|
+
(el as unknown as HTMLSelectElement).value)
|
|
182
|
+
: input.value;
|
|
183
|
+
const value = (raw ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
184
|
+
this.#buffer += ` [${tag === "SELECT" ? "select" : "input"} ${name}: ${JSON.stringify(value)}]`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#pre(el: HTMLElement) {
|
|
188
|
+
this.#flush();
|
|
189
|
+
const raw = (el.textContent ?? "").trim();
|
|
190
|
+
if (!raw) return;
|
|
191
|
+
const rows = raw.split("\n");
|
|
192
|
+
this.#lines.push(
|
|
193
|
+
"```",
|
|
194
|
+
...rows.slice(0, 30),
|
|
195
|
+
...(rows.length > 30 ? [`… ${rows.length - 30} more lines`] : []),
|
|
196
|
+
"```",
|
|
197
|
+
);
|
|
198
|
+
this.#length += raw.length;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#flush(prefix = "") {
|
|
202
|
+
const text = this.#buffer.replace(/\s+/g, " ").trim();
|
|
203
|
+
this.#buffer = "";
|
|
204
|
+
if (!text) return;
|
|
205
|
+
this.#lines.push(prefix + text);
|
|
206
|
+
this.#length += prefix.length + text.length;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { type Cls, FIELD_META, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
|
|
2
|
+
import { capitalize, Logger } from "akanjs/common";
|
|
3
|
+
import { ConstantRegistry } from "akanjs/constant";
|
|
4
|
+
import type { AgentRefusal, SerializedArg, SerializedSignal } from "akanjs/signal";
|
|
5
|
+
|
|
6
|
+
import { AgentCatalogue } from "../../signal/agent/AgentCatalogue";
|
|
7
|
+
import { databaseStateModelTypes, databaseStateNames } from "../databaseStateNames";
|
|
8
|
+
import { formSetterNames } from "../formSetterNames";
|
|
9
|
+
import type { SliceActionKey } from "../sliceRole";
|
|
10
|
+
import type { SliceStateKey } from "../state";
|
|
11
|
+
import type { StoreInstance } from "../storeInstance";
|
|
12
|
+
import { AgentVisibility } from "./AgentVisibility";
|
|
13
|
+
import type { SerializedStore, SerializedStoreAction, SerializedStoreState } from "./types";
|
|
14
|
+
|
|
15
|
+
/** A model field as the catalogue reads it. The parts of `FieldProps` a store setter's argument comes from. */
|
|
16
|
+
interface CatalogueField {
|
|
17
|
+
fieldType?: string;
|
|
18
|
+
isClass?: boolean;
|
|
19
|
+
isMap?: boolean;
|
|
20
|
+
isArray?: boolean;
|
|
21
|
+
arrDepth?: number;
|
|
22
|
+
modelRef?: Cls;
|
|
23
|
+
enum?: { refName: string };
|
|
24
|
+
example?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What each generated slice action takes, in the same terms an endpoint states its arguments. */
|
|
28
|
+
const sliceActionArgs: {
|
|
29
|
+
[key in SliceActionKey]: { effect: "state" | "query"; args: "slice" | SerializedArg[] };
|
|
30
|
+
} = {
|
|
31
|
+
initModel: { effect: "query", args: "slice" },
|
|
32
|
+
refreshModel: { effect: "query", args: [] },
|
|
33
|
+
|
|
34
|
+
selectModel: { effect: "state", args: [] },
|
|
35
|
+
setPageOfModel: { effect: "query", args: [{ type: "param", name: "page", refName: "Int" }] },
|
|
36
|
+
addPageOfModel: { effect: "query", args: [{ type: "param", name: "page", refName: "Int" }] },
|
|
37
|
+
setLimitOfModel: { effect: "query", args: [{ type: "param", name: "limit", refName: "Int" }] },
|
|
38
|
+
setQueryArgsOfModel: { effect: "query", args: "slice" },
|
|
39
|
+
setSortOfModel: { effect: "query", args: [{ type: "param", name: "sort", refName: "String" }] },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Which of the model's classes each slice state key holds. The rest hold a primitive or a query descriptor. */
|
|
43
|
+
const sliceStateModelTypes: { [key in SliceStateKey]?: SerializedStoreState["modelType"] } = {
|
|
44
|
+
defaultModel: "full",
|
|
45
|
+
modelList: "light",
|
|
46
|
+
modelInitList: "light",
|
|
47
|
+
modelSelection: "light",
|
|
48
|
+
modelInsight: "insight",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* What one built store offers an agent, derived from the store the browser is already running.
|
|
53
|
+
*
|
|
54
|
+
* The store is the audience-neutral half of the client surface the way a signal registry is the server's: a key on
|
|
55
|
+
* `st.do` is the same call the user's own click makes, so an agent that drives it cannot reach past what the UI
|
|
56
|
+
* already permits. That is why the default here is the opposite of the MCP catalogue's — every key is published
|
|
57
|
+
* unless something about it cannot be described, and each of those is recorded as a refusal rather than dropped.
|
|
58
|
+
*
|
|
59
|
+
* This is the visible universe, not the live view: a store's own `static agent` declaration is honored here, and
|
|
60
|
+
* the bridge narrows the result to the stores the rendered screen is reading when it answers.
|
|
61
|
+
*
|
|
62
|
+
* Nothing is re-declared. An action's arguments come from the endpoint it is named after, from the field metadata
|
|
63
|
+
* the form setter was generated from, or from the role the store recorded while building the slice; a key that
|
|
64
|
+
* matches none of those three is published only when it takes no arguments at all.
|
|
65
|
+
*/
|
|
66
|
+
export class StoreCatalogue {
|
|
67
|
+
readonly store: SerializedStore;
|
|
68
|
+
readonly refusals: AgentRefusal[] = [];
|
|
69
|
+
readonly visibility = new AgentVisibility();
|
|
70
|
+
|
|
71
|
+
readonly #instance: StoreInstance;
|
|
72
|
+
readonly #endpoints = new Map<string, { endpoint: SerializedSignal["endpoint"][string]; refName: string }>();
|
|
73
|
+
readonly #refused = new Set<string>();
|
|
74
|
+
readonly #baseFieldSetters = new Set<string>();
|
|
75
|
+
#formSetterCache: Map<string, SerializedStoreAction> | null = null;
|
|
76
|
+
static readonly #baseDocumentFields = new Set(["id", "createdAt", "updatedAt", "removedAt"]);
|
|
77
|
+
static readonly #warnedMismatches = new Set<string>();
|
|
78
|
+
|
|
79
|
+
constructor(instance: StoreInstance, serializedSignal: Record<string, SerializedSignal>) {
|
|
80
|
+
this.#instance = instance;
|
|
81
|
+
for (const { key, endpoint, refName } of AgentCatalogue.candidates(serializedSignal))
|
|
82
|
+
this.#endpoints.set(key, { endpoint, refName });
|
|
83
|
+
this.#refuseDeclaredExposures();
|
|
84
|
+
this.store = { state: this.#state(), action: this.#actions() };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** One line per opted-out store, so the Dock answers "why is X missing" without a row per hidden key. */
|
|
88
|
+
#refuseDeclaredExposures() {
|
|
89
|
+
for (const [refName, exposure] of this.visibility.declaredExposures()) {
|
|
90
|
+
if (exposure === false)
|
|
91
|
+
this.#refuse(refName, "its store declares `agent: false`, so none of its keys or actions are published.");
|
|
92
|
+
else for (const name of exposure.exclude) this.#refuse(name, "excluded by its store's `agent` declaration.");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#refuse(key: string, reason: string) {
|
|
97
|
+
if (this.#refused.has(key)) return;
|
|
98
|
+
this.#refused.add(key);
|
|
99
|
+
this.refusals.push({ key, reason });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#state(): { [key: string]: SerializedStoreState } {
|
|
103
|
+
const state = this.#instance.get();
|
|
104
|
+
const declared = StoreCatalogue.#declaredStateModels();
|
|
105
|
+
const entries = Object.keys(state)
|
|
106
|
+
.filter((key) => this.visibility.visibleKey(key))
|
|
107
|
+
.sort()
|
|
108
|
+
.map((key): [string, SerializedStoreState] => {
|
|
109
|
+
const role = this.#instance.sliceStateRoles.get(key);
|
|
110
|
+
const model = role
|
|
111
|
+
? { refName: role.refName, ...StoreCatalogue.#modelTypeOf(sliceStateModelTypes[role.role]) }
|
|
112
|
+
: (declared.get(key) ?? StoreCatalogue.#refNameOf(state[key]));
|
|
113
|
+
return [
|
|
114
|
+
key,
|
|
115
|
+
{
|
|
116
|
+
type: StoreCatalogue.#typeOf(state[key]),
|
|
117
|
+
...model,
|
|
118
|
+
...(role ? { role: role.role } : {}),
|
|
119
|
+
derived: this.#instance.derivedKeys.has(key),
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
});
|
|
123
|
+
return Object.fromEntries(entries);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The model each generated state key holds, taken from the declaration rather than from the value.
|
|
128
|
+
*
|
|
129
|
+
* A read has to be masked by the model, and the value cannot supply it: `immerify` copies a form into a plain
|
|
130
|
+
* object, so `<model>Form` — an `Input` holding whatever the user typed — arrives with its class already gone.
|
|
131
|
+
*/
|
|
132
|
+
static #declaredStateModels() {
|
|
133
|
+
const declared = new Map<string, { refName: string; modelType: SerializedStoreState["modelType"] }>();
|
|
134
|
+
for (const refName of ConstantRegistry.database.keys()) {
|
|
135
|
+
const names = databaseStateNames(refName);
|
|
136
|
+
for (const [role, modelType] of Object.entries(databaseStateModelTypes))
|
|
137
|
+
declared.set(names[role as keyof typeof names], { refName, modelType });
|
|
138
|
+
}
|
|
139
|
+
return declared;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static #modelTypeOf(modelType: SerializedStoreState["modelType"]) {
|
|
143
|
+
return modelType ? { modelType } : {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#actions(): { [key: string]: SerializedStoreAction } {
|
|
147
|
+
|
|
148
|
+
this.#formSetterCache = this.#formSetters();
|
|
149
|
+
const entries = Object.keys(this.#instance.do)
|
|
150
|
+
.sort()
|
|
151
|
+
.map((key): [string, SerializedStoreAction] | null => this.#action(key))
|
|
152
|
+
.filter((entry): entry is [string, SerializedStoreAction] => !!entry);
|
|
153
|
+
return Object.fromEntries(entries);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#action(key: string): [string, SerializedStoreAction] | null {
|
|
157
|
+
if (this.#refused.has(key)) return null;
|
|
158
|
+
|
|
159
|
+
if (this.#instance.generatedSetters.has(key)) return null;
|
|
160
|
+
|
|
161
|
+
if (this.#baseFieldSetters.has(key)) return null;
|
|
162
|
+
if (!this.visibility.visibleAction(key, this.#instance.actionOwners.get(key)?.refName)) return null;
|
|
163
|
+
const endpoint = this.#endpoints.get(key);
|
|
164
|
+
if (endpoint)
|
|
165
|
+
return this.#actionFitsEndpoint(key, endpoint.endpoint) ? [key, this.#endpointAction(key, endpoint)] : null;
|
|
166
|
+
const formSetter = this.#formSetterCache?.get(key);
|
|
167
|
+
if (formSetter) return [key, formSetter];
|
|
168
|
+
const slice = this.#sliceAction(key);
|
|
169
|
+
if (slice) return [key, slice];
|
|
170
|
+
return this.#plainAction(key);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The borrowed schema is only honest when the action can consume it: one that declares fewer parameters than the
|
|
175
|
+
* endpoint silently drops the tail and reads stale form state instead, so a schema-correct call becomes a wrong
|
|
176
|
+
* mutation reported as success. More parameters than the endpoint is the generated shape —
|
|
177
|
+
* `create<Model>(data, options?)` — and stays published. `Function.length` stops at the first default, so a
|
|
178
|
+
* defaulted mirror parameter reads as missing here; align it with the endpoint or exclude the action.
|
|
179
|
+
*/
|
|
180
|
+
#actionFitsEndpoint(key: string, endpoint: SerializedSignal["endpoint"][string]) {
|
|
181
|
+
const arity = this.#instance.actionArity.get(key) ?? 0;
|
|
182
|
+
const declared = endpoint.args.length;
|
|
183
|
+
if (arity >= declared) return true;
|
|
184
|
+
this.#refuse(
|
|
185
|
+
key,
|
|
186
|
+
`it declares ${arity} parameter${arity === 1 ? "" : "s"} while the same-named endpoint takes ${declared} — a schema-shaped call would drop the tail and read form state instead. Align the action's parameters with the endpoint, or exclude the action.`,
|
|
187
|
+
);
|
|
188
|
+
if (!StoreCatalogue.#warnedMismatches.has(key)) {
|
|
189
|
+
StoreCatalogue.#warnedMismatches.add(key);
|
|
190
|
+
Logger.warn(
|
|
191
|
+
`st.do.${key} does not mirror endpoint ${key} (${arity} vs ${declared} args) — not published to agents.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* An action named after an endpoint takes the endpoint's arguments — the house naming rule ("the signal, store,
|
|
199
|
+
* and dictionary re-add the model, so `st.do.X` reads the same as `fetch.X`") is where the store's schemas come
|
|
200
|
+
* from for free, and #actionFitsEndpoint is what holds the rule to its word.
|
|
201
|
+
*/
|
|
202
|
+
#endpointAction(
|
|
203
|
+
key: string,
|
|
204
|
+
{ endpoint, refName }: { endpoint: SerializedSignal["endpoint"][string]; refName: string },
|
|
205
|
+
) {
|
|
206
|
+
const effect = endpoint.type === "mutation" ? "mutation" : "query";
|
|
207
|
+
return { args: endpoint.args, effect, refName, endpoint: key } satisfies SerializedStoreAction;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The generated field setters, computed forward from the same field metadata `makeFormSetter` generated them from.
|
|
212
|
+
*
|
|
213
|
+
* A `hidden` or `secret` field is refused. Nothing else in the framework lets one of those cross an agent
|
|
214
|
+
* boundary — `resolveReturn` strips them on the way out and `Msg.mask` refuses a payload carrying them — and a
|
|
215
|
+
* setter is the same boundary facing the other way: publishing `setPasswordOnUser` names the field and invites a
|
|
216
|
+
* write to it in one entry.
|
|
217
|
+
*/
|
|
218
|
+
#formSetters(): Map<string, SerializedStoreAction> {
|
|
219
|
+
const setters = new Map<string, SerializedStoreAction>();
|
|
220
|
+
for (const [refName, cnst] of ConstantRegistry.database) {
|
|
221
|
+
const className = capitalize(refName);
|
|
222
|
+
const fields = (cnst.full as unknown as { [key: symbol]: Record<string, CatalogueField> })[FIELD_META];
|
|
223
|
+
if (!fields) continue;
|
|
224
|
+
for (const [field, meta] of Object.entries(fields)) {
|
|
225
|
+
const names = formSetterNames(className, field);
|
|
226
|
+
if (StoreCatalogue.#baseDocumentFields.has(field)) {
|
|
227
|
+
for (const name of Object.values(names)) if (name in this.#instance.do) this.#baseFieldSetters.add(name);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!(names.setFieldOnModel in this.#instance.do)) continue;
|
|
231
|
+
if (names.uploadFieldOnModel in this.#instance.do)
|
|
232
|
+
this.#refuse(names.uploadFieldOnModel, "it takes a browser `FileList`, which an agent has no way to hold.");
|
|
233
|
+
const arg = this.#argOfField(names.setFieldOnModel, field, meta);
|
|
234
|
+
if (!arg) continue;
|
|
235
|
+
const base = { effect: "state", refName, field } satisfies Partial<SerializedStoreAction>;
|
|
236
|
+
setters.set(names.setFieldOnModel, { ...base, role: "set", args: [arg] });
|
|
237
|
+
if (!meta.isArray) continue;
|
|
238
|
+
const element = { ...arg, ...(arg.arrDepth && arg.arrDepth > 1 ? { arrDepth: arg.arrDepth - 1 } : {}) };
|
|
239
|
+
if (arg.arrDepth === 1) delete element.arrDepth;
|
|
240
|
+
setters.set(names.addFieldOnModel, { ...base, role: "add", args: [element] });
|
|
241
|
+
setters.set(names.addOrSubFieldOnModel, { ...base, role: "addOrSub", args: [element] });
|
|
242
|
+
setters.set(names.subFieldOnModel, {
|
|
243
|
+
...base,
|
|
244
|
+
role: "sub",
|
|
245
|
+
args: [{ type: "param", name: "idx", refName: "Int" }],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return setters;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#argOfField(key: string, name: string, field: CatalogueField): SerializedArg | null {
|
|
253
|
+
if (field.fieldType === "hidden" || field.fieldType === "secret") {
|
|
254
|
+
this.#refuse(key, `\`${name}\` is a ${field.fieldType} field, which never crosses an agent boundary.`);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
if (field.isMap) {
|
|
258
|
+
this.#refuse(key, `\`${name}\` is a Map, which has no argument schema to publish.`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const arrDepth = field.arrDepth ?? 0;
|
|
262
|
+
if (field.enum)
|
|
263
|
+
return { type: "body", name, refName: "String", enum: field.enum.refName, ...(arrDepth ? { arrDepth } : {}) };
|
|
264
|
+
if (field.isClass) {
|
|
265
|
+
const model = field.modelRef ? ConstantRegistry.getRefName(field.modelRef, { allowEmpty: true }) : undefined;
|
|
266
|
+
this.#refuse(
|
|
267
|
+
key,
|
|
268
|
+
`\`${name}\` takes ${model ? `a \`${model}\`` : "an object"}, which an agent holding an id cannot build — it is chosen through the UI.`,
|
|
269
|
+
);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (!field.modelRef || !PrimitiveRegistry.has(field.modelRef)) {
|
|
273
|
+
this.#refuse(key, `\`${name}\` has no primitive to describe it.`);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
type: "body",
|
|
278
|
+
name,
|
|
279
|
+
refName: PrimitiveRegistry.getName(field.modelRef as typeof PrimitiveScalar),
|
|
280
|
+
...(arrDepth ? { arrDepth } : {}),
|
|
281
|
+
...(StoreCatalogue.#exampleOf(field.example) ?? {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#sliceAction(key: string): SerializedStoreAction | null {
|
|
286
|
+
const role = this.#instance.sliceActionRoles.get(key);
|
|
287
|
+
if (!role) return null;
|
|
288
|
+
if (role.role === "selectModel") {
|
|
289
|
+
this.#refuse(key, "it takes the list item itself, and an agent holding an id would store a stub in its place.");
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const { effect, args } = sliceActionArgs[role.role];
|
|
293
|
+
return { args: args === "slice" ? role.args : args, effect, refName: role.refName, role: role.role };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Everything else — a state setter the store generated for a plain key, and the actions a module wrote itself.
|
|
298
|
+
*
|
|
299
|
+
* Published only when it declares no parameters, which is the shape most custom actions have: the data comes from
|
|
300
|
+
* the form state the agent has already filled, in the same order a person fills it. One that does declare
|
|
301
|
+
* parameters and matched none of the three schema sources cannot be called safely, so it is refused by name.
|
|
302
|
+
*
|
|
303
|
+
* `Function.length` is the arity, so a rest parameter reads as none — the one shape that slips through. It slips
|
|
304
|
+
* through as callable with no arguments, which for a rest signature is the empty call.
|
|
305
|
+
*/
|
|
306
|
+
#plainAction(key: string): [string, SerializedStoreAction] | null {
|
|
307
|
+
const arity = this.#instance.actionArity.get(key) ?? 0;
|
|
308
|
+
if (arity > 0) {
|
|
309
|
+
this.#refuse(key, `it declares ${arity} argument${arity > 1 ? "s" : ""} that no endpoint or field describes.`);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const refName = this.#instance.actionOwners.get(key)?.refName;
|
|
313
|
+
return [key, { args: [], effect: "state", ...(refName ? { refName } : {}) }];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
static #typeOf(value: unknown): SerializedStoreState["type"] {
|
|
317
|
+
if (value === null || value === undefined) return "unknown";
|
|
318
|
+
if (Array.isArray(value)) return "list";
|
|
319
|
+
if (value instanceof Map) return "map";
|
|
320
|
+
if (value instanceof Date) return "date";
|
|
321
|
+
switch (typeof value) {
|
|
322
|
+
case "string":
|
|
323
|
+
return "string";
|
|
324
|
+
case "number":
|
|
325
|
+
return "number";
|
|
326
|
+
case "boolean":
|
|
327
|
+
return "boolean";
|
|
328
|
+
case "object":
|
|
329
|
+
return StoreCatalogue.#isList(value) ? "list" : "object";
|
|
330
|
+
default:
|
|
331
|
+
return "unknown";
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** `DataList` and `dayjs` are the two objects a store holds that are not what `typeof` says they are. */
|
|
336
|
+
static #isList(value: object) {
|
|
337
|
+
return "values" in value && Array.isArray((value as { values: unknown }).values);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
static #refNameOf(value: unknown) {
|
|
341
|
+
if (!value || typeof value !== "object") return {};
|
|
342
|
+
const refName = ConstantRegistry.getRefName(value.constructor as Cls, { allowEmpty: true });
|
|
343
|
+
return refName ? { refName } : {};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
static #exampleOf(example: unknown) {
|
|
347
|
+
if (example === null || example === undefined) return null;
|
|
348
|
+
if (["string", "number", "boolean"].includes(typeof example)) return { example: example as string };
|
|
349
|
+
return example instanceof Date ? { example } : null;
|
|
350
|
+
}
|
|
351
|
+
}
|