akanjs 3.0.0-alpha.24 → 3.0.0-alpha.26
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/client/clientRuntime.ts +2 -0
- package/dictionary/base.dictionary.ts +5 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/signal/serializer/fetch.serializer.ts +4 -0
- package/store/agent/AgentBridge.ts +30 -266
- package/store/agent/AgentContext.ts +3 -6
- package/store/agent/StoreCatalogue.ts +16 -251
- package/store/agent/StoreSurfaceSource.ts +8 -35
- package/store/agent/index.ts +0 -1
- package/store/agent/types.ts +0 -34
- package/store/agentic/StToolBuilder.ts +4 -3
- package/store/baseSt.ts +0 -7
- package/store/formSetterNames.ts +3 -3
- package/store/store.ts +1 -10
- package/store/types.ts +1 -7
- package/types/client/clientRuntime.d.ts +2 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/store/agent/AgentBridge.d.ts +19 -59
- package/types/store/agent/AgentContext.d.ts +2 -2
- package/types/store/agent/StoreCatalogue.d.ts +16 -16
- package/types/store/agent/StoreSurfaceSource.d.ts +6 -7
- package/types/store/agent/index.d.ts +0 -1
- package/types/store/agent/types.d.ts +0 -34
- package/types/store/baseSt.d.ts +0 -7
- package/types/store/formSetterNames.d.ts +3 -3
- package/types/store/store.d.ts +1 -2
- package/types/store/types.d.ts +1 -8
- package/types/ui/Agent/Dock.d.ts +9 -8
- package/types/ui/Agent/Tool.d.ts +6 -6
- package/types/ui/Agent/Transcript.d.ts +1 -1
- package/types/ui/Agent/index.d.ts +1 -1
- package/types/ui/Data/Dashboard.d.ts +3 -2
- package/types/ui/Data/Insight.d.ts +1 -1
- package/types/ui/Data/ListContainer.d.ts +3 -3
- package/types/ui/Data/dataExport.d.ts +5 -0
- package/types/ui/Data/dataText.d.ts +6 -0
- package/types/ui/Model/AdminPanel.d.ts +6 -2
- package/types/vendor/use-agentic/AgenticSurface.d.ts +3 -1
- package/types/vendor/use-agentic/types.d.ts +7 -0
- package/ui/Agent/Dock.tsx +20 -17
- package/ui/Agent/Tool.tsx +18 -11
- package/ui/Agent/Transcript.tsx +1 -1
- package/ui/BottomSheet.tsx +1 -1
- package/ui/Data/CardList.tsx +14 -3
- package/ui/Data/Dashboard.tsx +34 -50
- package/ui/Data/Insight.tsx +18 -12
- package/ui/Data/Item.tsx +42 -56
- package/ui/Data/ListContainer.tsx +116 -116
- package/ui/Data/TableList.tsx +22 -13
- package/ui/Data/dataExport.ts +54 -0
- package/ui/Data/dataText.ts +22 -0
- package/ui/KeyboardAvoiding.tsx +2 -2
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Link/CsrLink.tsx +1 -1
- package/ui/Menu.tsx +1 -1
- package/ui/Model/AdminPanel.tsx +29 -11
- package/ui/Signal/Doc.tsx +3 -3
- package/ui/Signal/RestApi.tsx +2 -2
- package/ui/System/CSR.tsx +1 -1
- package/ui/System/Client.tsx +5 -5
- package/ui/System/DevModeToggle.tsx +1 -1
- package/ui/System/Gtag.tsx +2 -2
- package/ui/System/Messages.tsx +2 -2
- package/ui/System/ThemeToggle.tsx +43 -63
- package/ui/Table.tsx +1 -1
- package/vendor/use-agentic/AgenticSurface.ts +19 -3
- package/vendor/use-agentic/types.ts +8 -0
- package/webkit/useFrameRuntime.ts +1 -1
- package/store/agent/AgentVisibility.ts +0 -68
- package/types/store/agent/AgentVisibility.d.ts +0 -21
package/client/clientRuntime.ts
CHANGED
|
@@ -47,6 +47,8 @@ type RuntimeFetch = typeof globalThis.fetch & {
|
|
|
47
47
|
serializedSignal: Record<string, unknown>;
|
|
48
48
|
setJwt: (jwt: string | null) => void;
|
|
49
49
|
slice: Record<string, SliceMeta>;
|
|
50
|
+
/** Sort keys per model refName, registered from each serialized signal's filter. */
|
|
51
|
+
sortKeyMap?: Map<string, string[]>;
|
|
50
52
|
ws: RuntimeWs;
|
|
51
53
|
[key: string]: unknown;
|
|
52
54
|
};
|
|
@@ -89,6 +89,11 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
89
89
|
removeMsg: ["Are you sure to remove?", "정말로 삭제하시겠습니까?"],
|
|
90
90
|
confirmMsg: ["Are you sure to {actionType}", "진행하시겠습니까? ({actionType})"],
|
|
91
91
|
perPage: ["/page", "/페이지"],
|
|
92
|
+
refresh: ["Refresh", "새로고침"],
|
|
93
|
+
cardView: ["Card view", "카드 보기"],
|
|
94
|
+
tableView: ["Table view", "표 보기"],
|
|
95
|
+
exportCsv: ["Export CSV", "CSV 내보내기"],
|
|
96
|
+
exportJson: ["Export JSON", "JSON 내보내기"],
|
|
92
97
|
actions: ["Actions", "작업"],
|
|
93
98
|
new: ["New", "신규"],
|
|
94
99
|
edit: ["Edit", "수정"],
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Cls, ENDPOINT_META, PrimitiveRegistry, type PrimitiveScalar, SLICE_META } from "akanjs/base";
|
|
2
2
|
import { Logger } from "akanjs/common";
|
|
3
3
|
import { ConstantRegistry, type ConstantType } from "akanjs/constant";
|
|
4
|
+
import { getFilterMeta } from "akanjs/document";
|
|
4
5
|
import type { LiveRegistry } from "akanjs/service";
|
|
5
6
|
import type {
|
|
6
7
|
ArgInfo,
|
|
@@ -90,9 +91,12 @@ export class FetchSerializer {
|
|
|
90
91
|
for (const [key, endpointInfo] of Object.entries(endpointMeta)) {
|
|
91
92
|
endpoint[key] = FetchSerializer.#serializeEndpoint(endpointInfo);
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
const sortKeys = Object.keys(getFilterMeta(sliceCls.srv.db.filter, { allowEmpty: true })?.sort ?? {});
|
|
93
96
|
return {
|
|
94
97
|
...(prefix ? { prefix } : {}),
|
|
95
98
|
...(Object.keys(slice).length ? { slice } : {}),
|
|
99
|
+
...(sortKeys.length ? { filter: { filter: {}, sortKeys } } : {}),
|
|
96
100
|
...(sliceCls.getGuards.filter((g) => g.name !== "None").length
|
|
97
101
|
? { getGuards: sliceCls.getGuards.map((g) => g.name) }
|
|
98
102
|
: {}),
|
|
@@ -1,147 +1,67 @@
|
|
|
1
1
|
import { DataList } from "akanjs/base";
|
|
2
|
-
import { Translator } from "akanjs/client";
|
|
3
|
-
import { parseAkanI18nEnv } from "akanjs/common";
|
|
4
2
|
import { ConstantRegistry, type MaskModel, mask } from "akanjs/constant";
|
|
5
|
-
import {
|
|
6
|
-
import type { AgentRefusal, JsonSchema, SerializedArg, SerializedSignal } from "akanjs/signal";
|
|
7
|
-
|
|
8
|
-
import { JsonSchemaBuilder } from "../../signal/schema/JsonSchemaBuilder";
|
|
3
|
+
import type { AgentRefusal } from "akanjs/signal";
|
|
9
4
|
import type { StoreInstance } from "../storeInstance";
|
|
10
5
|
import { StoreRegistry } from "../storeRegistry";
|
|
11
|
-
import type { AgentVisibility } from "./AgentVisibility";
|
|
12
6
|
import { StoreCatalogue } from "./StoreCatalogue";
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
export interface AgentTool {
|
|
16
|
-
name: string;
|
|
17
|
-
title?: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
/** One flat named object, the shape MCP publishes. The bridge maps it onto the action's positional parameters. */
|
|
20
|
-
inputSchema: JsonSchema;
|
|
21
|
-
effect: StoreActionEffect;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** One call the agent made, in the order it made them. */
|
|
25
|
-
export interface AgentCall {
|
|
26
|
-
name: string;
|
|
27
|
-
args: Record<string, unknown>;
|
|
28
|
-
at: Date;
|
|
29
|
-
error?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AgentBridgeOptions {
|
|
33
|
-
/** Resolves a dictionary key to its text. Defaults to the seeded `Translator` in the active locale. */
|
|
34
|
-
resolveDescription?: (key: string) => string | undefined;
|
|
35
|
-
}
|
|
7
|
+
import type { SerializedStoreState } from "./types";
|
|
36
8
|
|
|
37
9
|
/**
|
|
38
|
-
* What an in-page agent may
|
|
10
|
+
* What an in-page agent may read out of the store the user is looking at.
|
|
39
11
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* publish, while this one is the user's own session, under their own credential, with them watching.
|
|
12
|
+
* Reads only. Driving the app is a component's declaration to make: `st.tool` binds a name and a schema to the
|
|
13
|
+
* same handler the control on screen calls, so what an agent can do is what the screen offers the user and nothing
|
|
14
|
+
* else. A store method that no component declared is not a lever this screen has — publishing one made the surface
|
|
15
|
+
* the bundle rather than the screen, and gave the model levers whose effect it could not see.
|
|
45
16
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
17
|
+
* A key is readable while a mounted component subscribes it through `st.use` / `st.sel` / `st.ref`, and its value
|
|
18
|
+
* is masked by the model that key declares. Masking is not optional even though the data mostly came from the
|
|
19
|
+
* server already masked: `<model>Form` holds what the *user* typed, credentials included, and an in-page agent
|
|
20
|
+
* ships what it reads to a remote model. The mask is by the declared model rather than by the value's class,
|
|
21
|
+
* because `immerify` copies a form into a plain object and the class is gone by the time anyone can ask.
|
|
49
22
|
*/
|
|
50
23
|
export class AgentBridge {
|
|
51
24
|
readonly refusals: AgentRefusal[];
|
|
52
25
|
|
|
53
26
|
readonly #instance: StoreInstance;
|
|
54
|
-
readonly #
|
|
55
|
-
readonly #options: AgentBridgeOptions;
|
|
56
|
-
readonly #schema = new JsonSchemaBuilder({ refPrefix: "#/$defs/" });
|
|
57
|
-
readonly #byName = new Map<string, SerializedStoreAction>();
|
|
58
|
-
readonly #calls: AgentCall[] = [];
|
|
59
|
-
readonly #visibility: AgentVisibility;
|
|
60
|
-
readonly #allTools: AgentTool[];
|
|
61
|
-
readonly #liveTools = new Map<string, { signature: string; tools: AgentTool[] }>();
|
|
27
|
+
readonly #state: { [key: string]: SerializedStoreState };
|
|
62
28
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*/
|
|
67
|
-
static of(options: AgentBridgeOptions = {}) {
|
|
68
|
-
return new AgentBridge(StoreRegistry.instance, FetchClient.sharedSerializedSignal, options);
|
|
29
|
+
/** The bridge for the app running in this process: the one store every `st.use` goes through. */
|
|
30
|
+
static of() {
|
|
31
|
+
return new AgentBridge(StoreRegistry.instance);
|
|
69
32
|
}
|
|
70
33
|
|
|
71
|
-
constructor(
|
|
72
|
-
instance: StoreInstance,
|
|
73
|
-
serializedSignal: Record<string, SerializedSignal>,
|
|
74
|
-
options: AgentBridgeOptions = {},
|
|
75
|
-
) {
|
|
34
|
+
constructor(instance: StoreInstance) {
|
|
76
35
|
this.#instance = instance;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.#store = catalogue.store;
|
|
36
|
+
const catalogue = new StoreCatalogue(instance);
|
|
37
|
+
this.#state = catalogue.state;
|
|
80
38
|
this.refusals = catalogue.refusals;
|
|
81
|
-
this.#visibility = catalogue.visibility;
|
|
82
|
-
for (const [name, action] of Object.entries(this.#store.action)) this.#byName.set(name, action);
|
|
83
|
-
this.#allTools = Object.entries(this.#store.action).map(([name, action]) => this.#tool(name, action));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The catalogued actions of the stores the rendered screen is reading right now — the live view, recomputed as
|
|
88
|
-
* components mount and unmount. Global exposure was the first cut's behavior and it published levers the screen
|
|
89
|
-
* does not respond to; the screen's own subscriptions are the honest boundary.
|
|
90
|
-
*/
|
|
91
|
-
get tools(): AgentTool[] {
|
|
92
|
-
return this.toolsFor("");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** The live view one zone session reads: owners counted from the keys that zone's own subtree subscribes. */
|
|
96
|
-
toolsFor(viewKey: string): AgentTool[] {
|
|
97
|
-
const live = this.#liveOwners(viewKey);
|
|
98
|
-
const signature = [...live].sort().join(",");
|
|
99
|
-
const cached = this.#liveTools.get(viewKey);
|
|
100
|
-
if (cached && cached.signature === signature) return cached.tools;
|
|
101
|
-
const tools = this.#allTools.filter((tool) => this.#ownerLive(tool.name, live));
|
|
102
|
-
this.#liveTools.set(viewKey, { signature, tools });
|
|
103
|
-
return tools;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get visibility(): AgentVisibility {
|
|
107
|
-
return this.#visibility;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
#liveOwners(viewKey = "") {
|
|
111
|
-
return this.#visibility.liveOwners(this.#instance.liveKeysIn(viewKey));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
#ownerLive(name: string, live: ReadonlySet<string>) {
|
|
115
|
-
const owner = this.#instance.actionOwners.get(name)?.refName;
|
|
116
|
-
return !owner || live.has(owner);
|
|
117
39
|
}
|
|
118
40
|
|
|
119
41
|
get state(): { [key: string]: SerializedStoreState } {
|
|
120
|
-
return this.#
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
get transcript(): readonly AgentCall[] {
|
|
124
|
-
return this.#calls;
|
|
42
|
+
return this.#state;
|
|
125
43
|
}
|
|
126
44
|
|
|
127
45
|
subscribe(listener: () => void) {
|
|
128
46
|
return this.#instance.subscribe(listener);
|
|
129
47
|
}
|
|
130
48
|
|
|
49
|
+
/** The keys one view may read right now: subscribed by a mounted component and catalogued. */
|
|
50
|
+
readableKeys(viewKey = ""): string[] {
|
|
51
|
+
return [...this.#instance.liveKeysIn(viewKey).keys()].filter((key) => !!this.#state[key]).sort();
|
|
52
|
+
}
|
|
53
|
+
|
|
131
54
|
/**
|
|
132
55
|
* The value behind a state key, stripped of what the model marks `hidden` or `secret`.
|
|
133
56
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* The mask is by the declared model rather than by the value's class, because `immerify` copies a form into a
|
|
137
|
-
* plain object and the class is gone by the time anyone can ask.
|
|
57
|
+
* The key itself has to be one the screen reads, not merely one its store owns: a component subscribing
|
|
58
|
+
* `userList` says the screen shows a user list, and says nothing about `userForm` sitting in the same store.
|
|
138
59
|
*/
|
|
139
60
|
read(key: string, viewKey = ""): unknown {
|
|
140
|
-
const entry = this.#
|
|
61
|
+
const entry = this.#state[key];
|
|
141
62
|
if (!entry) throw new Error(`Unknown state key: ${key}`);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new Error(`State key "${key}" is not part of the current screen's surface.`);
|
|
63
|
+
if (!this.#instance.liveKeysIn(viewKey).has(key))
|
|
64
|
+
throw new Error(`State key "${key}" is not read by this screen, so it is not part of its surface.`);
|
|
145
65
|
const value = AgentBridge.#unwrap(this.#instance.get()[key]);
|
|
146
66
|
if (entry.refName && entry.modelType) {
|
|
147
67
|
const model = ConstantRegistry.getModelRef(entry.refName, entry.modelType) as MaskModel;
|
|
@@ -153,162 +73,6 @@ export class AgentBridge {
|
|
|
153
73
|
);
|
|
154
74
|
}
|
|
155
75
|
|
|
156
|
-
/**
|
|
157
|
-
* Dispatches through `st.do`, so what happens is what happens when the user clicks.
|
|
158
|
-
*
|
|
159
|
-
* Arguments arrive named and are mapped onto the action's parameters in declared order. An omitted optional one
|
|
160
|
-
* becomes `null`, which is what the slice query builders already expect; an omitted required one is refused,
|
|
161
|
-
* because the alternative is a call that writes `undefined` into state and reports success.
|
|
162
|
-
*/
|
|
163
|
-
async call(name: string, args: Record<string, unknown> = {}, viewKey = "") {
|
|
164
|
-
const action = this.#byName.get(name);
|
|
165
|
-
if (!action) throw new Error(`Unknown action: ${name}`);
|
|
166
|
-
|
|
167
|
-
const record: AgentCall = { name, args, at: new Date() };
|
|
168
|
-
this.#calls.push(record);
|
|
169
|
-
try {
|
|
170
|
-
if (!this.#ownerLive(name, this.#liveOwners(viewKey)))
|
|
171
|
-
throw new Error(`Action "${name}" is not part of the current screen's surface.`);
|
|
172
|
-
const positional = action.args.map((arg) => this.#value(name, arg, args));
|
|
173
|
-
await this.#instance.do[name]?.(...positional);
|
|
174
|
-
} catch (error) {
|
|
175
|
-
record.error = error instanceof Error ? error.message : String(error);
|
|
176
|
-
throw error;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
#value(name: string, arg: SerializedArg, args: Record<string, unknown>) {
|
|
181
|
-
if (!(arg.name in args) || args[arg.name] === undefined) {
|
|
182
|
-
if (arg.nullable || arg.type === "search") return null;
|
|
183
|
-
throw new Error(`Missing argument "${arg.name}" for ${name}.`);
|
|
184
|
-
}
|
|
185
|
-
return AgentBridge.#checked(name, arg, args[arg.name]);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Checks a value against what the argument declared, one level of array included.
|
|
190
|
-
*
|
|
191
|
-
* `st.do` accepts anything — it is a rest wrapper — so without this a string where an `Int` belongs is written
|
|
192
|
-
* into state and rendered, and the agent is told the call succeeded. A model argument is checked only for being
|
|
193
|
-
* an object: the published schema describes its fields and the endpoint validates them server-side.
|
|
194
|
-
*/
|
|
195
|
-
static #checked(name: string, arg: SerializedArg, value: unknown): unknown {
|
|
196
|
-
const depth = arg.arrDepth ?? 0;
|
|
197
|
-
if (depth) {
|
|
198
|
-
if (!Array.isArray(value)) throw new Error(`Argument "${arg.name}" of ${name} must be an array.`);
|
|
199
|
-
return value.map((item) => AgentBridge.#checked(name, { ...arg, arrDepth: depth - 1 }, item));
|
|
200
|
-
}
|
|
201
|
-
if (value === null) return null;
|
|
202
|
-
if (arg.enum) return AgentBridge.#checkedEnum(name, arg, value);
|
|
203
|
-
switch (arg.refName) {
|
|
204
|
-
case "String":
|
|
205
|
-
case "ID":
|
|
206
|
-
return AgentBridge.#assertType(name, arg, value, "string");
|
|
207
|
-
case "Int":
|
|
208
|
-
if (!Number.isInteger(value)) throw new Error(`Argument "${arg.name}" of ${name} must be a whole number.`);
|
|
209
|
-
return value;
|
|
210
|
-
case "Float":
|
|
211
|
-
if (typeof value !== "number" || !Number.isFinite(value))
|
|
212
|
-
throw new Error(`Argument "${arg.name}" of ${name} must be a finite number.`);
|
|
213
|
-
return value;
|
|
214
|
-
case "Boolean":
|
|
215
|
-
return AgentBridge.#assertType(name, arg, value, "boolean");
|
|
216
|
-
case "Date":
|
|
217
|
-
return AgentBridge.#checkedDate(name, arg, value);
|
|
218
|
-
default:
|
|
219
|
-
if (typeof value !== "object") throw new Error(`Argument "${arg.name}" of ${name} must be an object.`);
|
|
220
|
-
return value;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
static #assertType(name: string, arg: SerializedArg, value: unknown, type: "string" | "boolean") {
|
|
225
|
-
if (typeof value !== type) throw new Error(`Argument "${arg.name}" of ${name} must be a ${type}.`);
|
|
226
|
-
return value;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
static #checkedEnum(name: string, arg: SerializedArg, value: unknown) {
|
|
230
|
-
const values = arg.enum ? ConstantRegistry.enum.get(arg.enum)?.values : undefined;
|
|
231
|
-
if (!values) return value;
|
|
232
|
-
if (!values.includes(value as never))
|
|
233
|
-
throw new Error(`Argument "${arg.name}" of ${name} must be one of: ${[...values].join(", ")}.`);
|
|
234
|
-
return value;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** An agent has no `Date`, so an ISO string is what it sends. Anything unparseable is refused rather than `Invalid Date`. */
|
|
238
|
-
static #checkedDate(name: string, arg: SerializedArg, value: unknown) {
|
|
239
|
-
if (value instanceof Date) return value;
|
|
240
|
-
const parsed = typeof value === "string" ? new Date(value) : null;
|
|
241
|
-
if (!parsed || Number.isNaN(parsed.getTime()))
|
|
242
|
-
throw new Error(`Argument "${arg.name}" of ${name} must be an ISO 8601 date string.`);
|
|
243
|
-
return parsed;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
#tool(name: string, action: SerializedStoreAction): AgentTool {
|
|
247
|
-
const properties = Object.fromEntries(action.args.map((arg) => [arg.name, this.#argSchema(action, arg)]));
|
|
248
|
-
const required = action.args.filter((arg) => !arg.nullable && arg.type !== "search").map((arg) => arg.name);
|
|
249
|
-
const defs = this.#schema.referencedSchemas(properties);
|
|
250
|
-
return {
|
|
251
|
-
name,
|
|
252
|
-
...this.#texts(action),
|
|
253
|
-
inputSchema: {
|
|
254
|
-
type: "object",
|
|
255
|
-
properties,
|
|
256
|
-
...(required.length ? { required } : {}),
|
|
257
|
-
additionalProperties: false,
|
|
258
|
-
...(Object.keys(defs).length ? { $defs: defs } : {}),
|
|
259
|
-
},
|
|
260
|
-
effect: action.effect,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* A store action carries no argument metadata of its own, so the prose comes from wherever the argument came
|
|
266
|
-
* from: an endpoint-named action borrows `<refName>.signal.<endpoint>.arg.<name>.desc`, and a field setter's one
|
|
267
|
-
* argument is the field, described at `<refName>.<field>.desc`. A slice role's `page`/`limit`/`sort` are the
|
|
268
|
-
* framework's own and have no dictionary entry to borrow.
|
|
269
|
-
*/
|
|
270
|
-
#argSchema(action: SerializedStoreAction, arg: SerializedArg) {
|
|
271
|
-
const description = this.#argText(action, arg);
|
|
272
|
-
return {
|
|
273
|
-
...this.#schema.arg(arg),
|
|
274
|
-
...(description ? { description } : {}),
|
|
275
|
-
...(arg.example !== undefined ? { examples: [arg.example] } : {}),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
#argText({ refName, endpoint, field }: SerializedStoreAction, arg: SerializedArg) {
|
|
280
|
-
if (!refName) return undefined;
|
|
281
|
-
if (endpoint) return this.#text(`${refName}.signal.${endpoint}.arg.${arg.name}.desc`);
|
|
282
|
-
return field && arg.name === field ? this.#text(`${refName}.${field}.desc`) : undefined;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* The words an agent picks the action by, borrowed from what the action is named after.
|
|
287
|
-
*
|
|
288
|
-
* The endpoint comes first, and it is right rather than merely adequate: the house rule makes `st.do.X` and
|
|
289
|
-
* `fetch.X` the same verb, so an action named after an endpoint reads as that endpoint's `.desc()`. A field
|
|
290
|
-
* setter falls back to the field's own label.
|
|
291
|
-
*/
|
|
292
|
-
#texts({ refName, endpoint, field }: SerializedStoreAction) {
|
|
293
|
-
const keys = refName
|
|
294
|
-
? [...(endpoint ? [`${refName}.signal.${endpoint}`] : []), ...(field ? [`${refName}.${field}`] : [])]
|
|
295
|
-
: [];
|
|
296
|
-
for (const key of keys) {
|
|
297
|
-
const title = this.#text(key);
|
|
298
|
-
const description = this.#text(`${key}.desc`);
|
|
299
|
-
if (title || description) return { ...(title ? { title } : {}), ...(description ? { description } : {}) };
|
|
300
|
-
}
|
|
301
|
-
return {};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
#text(key: string) {
|
|
305
|
-
if (this.#options.resolveDescription) return this.#options.resolveDescription(key);
|
|
306
|
-
const locale = Translator.getActiveLocale() ?? parseAkanI18nEnv().defaultLocale;
|
|
307
|
-
const text = Translator.translateByLocale(locale, key);
|
|
308
|
-
|
|
309
|
-
return text === key ? undefined : text;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
76
|
static #unwrap(value: unknown) {
|
|
313
77
|
return value instanceof DataList ? value.values : value;
|
|
314
78
|
}
|
|
@@ -8,8 +8,8 @@ import { ensureStoreSurface } from "./storeSurface";
|
|
|
8
8
|
* The akan default context for a turn: where the user is (route), what is on screen (scopes and their curated
|
|
9
9
|
* resources), and which store keys the mounted components are reading (live keys). Live entries carry names and
|
|
10
10
|
* small primitives only — anything bigger is one `readState` call away, masked — so the block cannot bloat with
|
|
11
|
-
* what happens to be in the store. Base-store
|
|
12
|
-
*
|
|
11
|
+
* what happens to be in the store. Base-store plumbing is kept off the surface by `st.use.x({ agent: false })`
|
|
12
|
+
* at each call site, and the route block carries the three of them that matter.
|
|
13
13
|
*/
|
|
14
14
|
export class AgentContext {
|
|
15
15
|
static of(): AgentContext {
|
|
@@ -40,10 +40,7 @@ export class AgentContext {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
#liveBlock(viewKey: string): ContextBlock[] {
|
|
43
|
-
const live =
|
|
44
|
-
.filter((key) => this.#bridge.visibility.visibleKey(key))
|
|
45
|
-
.sort()
|
|
46
|
-
.map((key) => this.#liveEntry(key));
|
|
43
|
+
const live = this.#bridge.readableKeys(viewKey).map((key) => this.#liveEntry(key));
|
|
47
44
|
return live.length ? [{ kind: "state", live, note: "Call readState(key) to read a value." }] : [];
|
|
48
45
|
}
|
|
49
46
|
|