akanjs 3.0.0-beta.16 → 3.0.0-beta.18
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/base/base.ts +1 -1
- package/client/csrTypes.ts +1 -31
- package/client/frameConfig.ts +2 -2
- package/client/route/RootLayoutDefinition.ts +0 -6
- package/client/route/RouteDefinition.ts +1 -13
- package/common/CodeTranscript.ts +12 -0
- package/common/codeAgentProfile.ts +54 -0
- package/common/codeAgentWire.ts +31 -0
- package/common/index.ts +5 -0
- package/common/routeConvention.ts +0 -3
- package/constant/constantRegistry.ts +8 -11
- package/document/dataLoader.ts +48 -15
- package/document/loaderInfo.ts +28 -7
- package/fetch/client/fetchClient.ts +14 -12
- package/fetch/client/httpClient.ts +1 -1
- 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/server/akanServer.ts +3 -1
- package/server/console.ts +9 -9
- package/server/decorators.ts +3 -64
- package/server/devtools/depsSerializer.ts +1 -4
- package/server/di/diLifecycle.ts +1 -3
- package/server/head.tsx +60 -0
- package/server/imageOptimizer.ts +3 -1
- package/server/resolver/database.resolver.ts +29 -26
- package/server/routeElementComposer.tsx +3 -3
- package/server/routeTreeBuilder.ts +1 -24
- package/server/rscWorker.tsx +10 -21
- package/server/webRouter.ts +12 -8
- package/service/injectInfo.ts +86 -72
- package/service/predefinedAdaptor/cache.adaptor.ts +42 -8
- package/service/predefinedAdaptor/storage.adaptor.ts +6 -6
- package/signal/endpointCache.ts +75 -0
- package/signal/guard.ts +4 -3
- package/signal/middleware.ts +0 -77
- package/signal/signalContext.ts +4 -14
- package/signal/types.ts +4 -5
- package/types/client/csrTypes.d.ts +1 -31
- package/types/client/route/RootLayoutDefinition.d.ts +0 -1
- package/types/client/route/RouteDefinition.d.ts +1 -3
- package/types/common/CodeTranscript.d.ts +3 -1
- package/types/common/codeAgentProfile.d.ts +54 -0
- package/types/common/codeAgentWire.d.ts +28 -0
- package/types/common/index.d.ts +2 -2
- package/types/constant/constantRegistry.d.ts +2 -2
- package/types/document/dataLoader.d.ts +6 -1
- package/types/document/loaderInfo.d.ts +9 -4
- package/types/server/console.d.ts +1 -1
- package/types/server/decorators.d.ts +1 -8
- package/types/server/head.d.ts +10 -0
- package/types/server/routeElementComposer.d.ts +1 -1
- package/types/service/injectInfo.d.ts +17 -14
- package/types/service/predefinedAdaptor/cache.adaptor.d.ts +1 -0
- package/types/service/predefinedAdaptor/storage.adaptor.d.ts +1 -2
- package/types/signal/endpointCache.d.ts +19 -0
- package/types/signal/guard.d.ts +4 -3
- package/types/signal/middleware.d.ts +0 -20
- package/types/signal/signalContext.d.ts +1 -6
- package/types/signal/types.d.ts +4 -5
- package/types/ui/Agent/Menu.d.ts +2 -2
- package/types/ui/Button.d.ts +1 -1
- package/types/ui/CsrImage.d.ts +2 -1
- package/types/ui/Data/Item.d.ts +1 -1
- package/types/ui/Link/CsrLink.d.ts +1 -1
- package/types/ui/Link/types.d.ts +2 -0
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +2 -3
- package/types/ui/System/Common.d.ts +0 -2
- package/types/ui/System/SSR.d.ts +2 -2
- package/types/ui/recipe/inputRecipe.d.ts +1 -1
- package/ui/Agent/Menu.tsx +2 -2
- package/ui/Button.tsx +1 -1
- package/ui/Constant/Doc.tsx +121 -77
- package/ui/Constant/Graph.tsx +11 -3
- package/ui/CsrImage.tsx +3 -2
- package/ui/Data/Item.tsx +1 -1
- package/ui/Field/Choice.tsx +1 -1
- package/ui/Field/Collection.tsx +4 -3
- package/ui/Image.tsx +1 -0
- package/ui/Link/CsrLink.tsx +5 -1
- package/ui/Link/types.ts +2 -0
- package/ui/System/CSR.tsx +1 -9
- package/ui/System/Client.tsx +2 -4
- package/ui/System/Common.tsx +0 -2
- package/ui/System/SSR.tsx +1 -9
- package/ui/recipe/inputRecipe.ts +1 -1
- package/webkit/bootCsr.tsx +0 -12
- package/webkit/useCamera.tsx +4 -3
- package/webkit/useCsrValues.ts +3 -3
- package/webkit/useFetch.ts +6 -7
- package/server/metadata.tsx +0 -198
- package/types/server/metadata.d.ts +0 -22
- package/types/ui/System/Gtag.d.ts +0 -4
- package/ui/System/Gtag.tsx +0 -69
package/base/base.ts
CHANGED
|
@@ -166,7 +166,7 @@ export class DataList<Light extends { id: string }> {
|
|
|
166
166
|
return this.values.flatMap(fn);
|
|
167
167
|
}
|
|
168
168
|
sort(fn: (a: Light, b: Light) => number) {
|
|
169
|
-
return new DataList(this.values.sort(fn));
|
|
169
|
+
return new DataList([...this.values].sort(fn));
|
|
170
170
|
}
|
|
171
171
|
filter(fn: (value: Light, idx: number) => boolean) {
|
|
172
172
|
return new DataList(this.values.filter(fn));
|
package/client/csrTypes.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface PageConfig {
|
|
|
46
46
|
ssr?: SsrRenderMode;
|
|
47
47
|
/**
|
|
48
48
|
* Opt in to guarded RSC page suffix commits when the page does not require
|
|
49
|
-
* head
|
|
49
|
+
* head updates and the retained route chain head is invariant for
|
|
50
50
|
* sibling navigations under the same layout.
|
|
51
51
|
*/
|
|
52
52
|
rscPatchHeadSafe?: boolean;
|
|
@@ -109,7 +109,6 @@ export interface AkanHeadSnapshotV1 {
|
|
|
109
109
|
}
|
|
110
110
|
export interface ResolvedHead {
|
|
111
111
|
node: Head | null | undefined;
|
|
112
|
-
hasExplicitLanguageAlternates: boolean;
|
|
113
112
|
headSnapshot?: AkanHeadSnapshotV1;
|
|
114
113
|
}
|
|
115
114
|
export type ResolveHeadResult = Head | ResolvedHead | null | undefined;
|
|
@@ -163,53 +162,24 @@ export interface WebAppManifest {
|
|
|
163
162
|
screenshots?: WebAppManifestIcon[];
|
|
164
163
|
[key: string]: unknown;
|
|
165
164
|
}
|
|
166
|
-
export interface AkanMetadata {
|
|
167
|
-
title?: string;
|
|
168
|
-
description?: string;
|
|
169
|
-
robots?: string;
|
|
170
|
-
openGraph?: {
|
|
171
|
-
title?: string;
|
|
172
|
-
description?: string;
|
|
173
|
-
type?: string;
|
|
174
|
-
url?: string;
|
|
175
|
-
siteName?: string;
|
|
176
|
-
images?: string | string[];
|
|
177
|
-
};
|
|
178
|
-
twitter?: {
|
|
179
|
-
card?: "summary" | "summary_large_image" | "app" | "player" | (string & {});
|
|
180
|
-
title?: string;
|
|
181
|
-
description?: string;
|
|
182
|
-
images?: string | string[];
|
|
183
|
-
};
|
|
184
|
-
alternates?: {
|
|
185
|
-
canonical?: string;
|
|
186
|
-
languages?: Record<string, string>;
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
export type GenerateMetadata = (props: PageProps) => PromiseOrObject<AkanMetadata | null | undefined>;
|
|
190
165
|
export interface PageModule {
|
|
191
166
|
default?: PageRender;
|
|
192
167
|
pageConfig?: PageConfig;
|
|
193
168
|
head?: Head;
|
|
194
|
-
metadata?: AkanMetadata;
|
|
195
169
|
generateHead?: GenerateHead;
|
|
196
|
-
generateMetadata?: GenerateMetadata;
|
|
197
170
|
Loading?: PageLoadingRender;
|
|
198
171
|
}
|
|
199
172
|
export interface LayoutModule {
|
|
200
173
|
default?: LayoutRender;
|
|
201
174
|
pageConfig?: PageConfig;
|
|
202
175
|
head?: Head;
|
|
203
|
-
metadata?: AkanMetadata;
|
|
204
176
|
generateHead?: GenerateHead;
|
|
205
|
-
generateMetadata?: GenerateMetadata;
|
|
206
177
|
fonts?: ReactFont[];
|
|
207
178
|
manifest?: WebAppManifest;
|
|
208
179
|
theme?: string;
|
|
209
180
|
reconnect?: boolean;
|
|
210
181
|
wsConnect?: boolean;
|
|
211
182
|
layoutStyle?: "mobile" | "web";
|
|
212
|
-
gaTrackingId?: string;
|
|
213
183
|
Loading?: LayoutLoadingRender;
|
|
214
184
|
NotFound?: LayoutNotFoundRender;
|
|
215
185
|
Error?: LayoutErrorRender;
|
package/client/frameConfig.ts
CHANGED
|
@@ -165,8 +165,8 @@ export function resolvePageState({
|
|
|
165
165
|
: (config.gesture ?? false),
|
|
166
166
|
cache: config.cache ?? false,
|
|
167
167
|
ssr: config.ssr ?? "stream",
|
|
168
|
-
topSafeAreaColor: config.topSafeAreaColor ??
|
|
169
|
-
bottomSafeAreaColor: config.bottomSafeAreaColor ??
|
|
168
|
+
topSafeAreaColor: config.topSafeAreaColor ?? defaultPageState.topSafeAreaColor,
|
|
169
|
+
bottomSafeAreaColor: config.bottomSafeAreaColor ?? defaultPageState.bottomSafeAreaColor,
|
|
170
170
|
};
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -12,7 +12,6 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
12
12
|
#reconnect?: boolean;
|
|
13
13
|
#wsConnect?: boolean;
|
|
14
14
|
#layoutStyle?: "mobile" | "web";
|
|
15
|
-
#gaTrackingId?: string;
|
|
16
15
|
|
|
17
16
|
fonts(fonts: ReactFont[]) {
|
|
18
17
|
this.#fonts = fonts;
|
|
@@ -38,10 +37,6 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
38
37
|
this.#layoutStyle = style;
|
|
39
38
|
return this;
|
|
40
39
|
}
|
|
41
|
-
gaTrackingId(id: string) {
|
|
42
|
-
this.#gaTrackingId = id;
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
40
|
|
|
46
41
|
protected override extendModule(module: PageModule & LayoutModule): PageModule & LayoutModule {
|
|
47
42
|
return {
|
|
@@ -52,7 +47,6 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
52
47
|
...(this.#reconnect !== undefined ? { reconnect: this.#reconnect } : {}),
|
|
53
48
|
...(this.#wsConnect !== undefined ? { wsConnect: this.#wsConnect } : {}),
|
|
54
49
|
...(this.#layoutStyle ? { layoutStyle: this.#layoutStyle } : {}),
|
|
55
|
-
...(this.#gaTrackingId ? { gaTrackingId: this.#gaTrackingId } : {}),
|
|
56
50
|
};
|
|
57
51
|
}
|
|
58
52
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { parseAkanI18nEnv } from "akanjs/common";
|
|
10
10
|
import { deserialize } from "akanjs/constant";
|
|
11
11
|
import type { ReactNode } from "react";
|
|
12
|
-
import type {
|
|
12
|
+
import type { Head, LayoutModule, PageConfig, PageModule } from "../csrTypes";
|
|
13
13
|
import { AkanNotFoundError } from "../router";
|
|
14
14
|
import { RouteArgError, type RouteArgInfo, type RouteArgInput, type RouteBaseArgs } from "./routeArgs";
|
|
15
15
|
|
|
@@ -19,7 +19,6 @@ export type RouteKind = "page" | "layout" | "rootLayout";
|
|
|
19
19
|
export const routeDefinitionMarker = Symbol.for("akan.routeDefinition");
|
|
20
20
|
|
|
21
21
|
type HeadStage<Args> = Head | ((args: Args) => PromiseOrObject<Head | null | undefined>);
|
|
22
|
-
type MetadataStage<Args> = AkanMetadata | ((args: Args) => PromiseOrObject<AkanMetadata | null | undefined>);
|
|
23
22
|
|
|
24
23
|
interface RouteRenderProps {
|
|
25
24
|
params: Record<string, string>;
|
|
@@ -46,7 +45,6 @@ export abstract class RouteDefinition<
|
|
|
46
45
|
#config?: PageConfig;
|
|
47
46
|
|
|
48
47
|
#head?: HeadStage<never>;
|
|
49
|
-
#metadata?: MetadataStage<never>;
|
|
50
48
|
#loading?: (args: never) => PromiseOrObject<ReactNode>;
|
|
51
49
|
#render?: (args: never) => PromiseOrObject<ReactNode>;
|
|
52
50
|
|
|
@@ -58,10 +56,6 @@ export abstract class RouteDefinition<
|
|
|
58
56
|
this.#head = head as HeadStage<never>;
|
|
59
57
|
return this;
|
|
60
58
|
}
|
|
61
|
-
metadata(metadata: MetadataStage<RouteBaseArgs & Args>) {
|
|
62
|
-
this.#metadata = metadata as MetadataStage<never>;
|
|
63
|
-
return this;
|
|
64
|
-
}
|
|
65
59
|
loading(render: (args: RouteBaseArgs & Args & Extra) => PromiseOrObject<ReactNode>) {
|
|
66
60
|
this.#loading = render as (args: never) => PromiseOrObject<ReactNode>;
|
|
67
61
|
return this;
|
|
@@ -136,7 +130,6 @@ export abstract class RouteDefinition<
|
|
|
136
130
|
if (!render)
|
|
137
131
|
throw new Error(`[route-convention] a ${this.kind}() chain ends with .render(), and this one has none`);
|
|
138
132
|
const head = this.#head;
|
|
139
|
-
const metadata = this.#metadata;
|
|
140
133
|
const loading = this.#loading;
|
|
141
134
|
const module: PageModule & LayoutModule = {
|
|
142
135
|
default: (async (props: RouteRenderProps) => await render(this.#argsOf(props) as never)) as never,
|
|
@@ -146,11 +139,6 @@ export abstract class RouteDefinition<
|
|
|
146
139
|
: typeof head === "function"
|
|
147
140
|
? { generateHead: async (props: RouteRenderProps) => await head(this.#argsOf(props) as never) }
|
|
148
141
|
: { head }),
|
|
149
|
-
...(metadata === undefined
|
|
150
|
-
? {}
|
|
151
|
-
: typeof metadata === "function"
|
|
152
|
-
? { generateMetadata: async (props: RouteRenderProps) => await metadata(this.#argsOf(props) as never) }
|
|
153
|
-
: { metadata }),
|
|
154
142
|
...(loading
|
|
155
143
|
? {
|
|
156
144
|
Loading: ((props: RouteRenderProps) =>
|
package/common/CodeTranscript.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CodeAgentQuestion,
|
|
5
5
|
CodeAgentSessionInfo,
|
|
6
6
|
CodeAgentStopReason,
|
|
7
|
+
CodeAgentSubagent,
|
|
7
8
|
CodeAgentToolOutcome,
|
|
8
9
|
CodeAgentToolSummary,
|
|
9
10
|
} from "akanjs/common";
|
|
@@ -46,6 +47,7 @@ export class CodeTranscript {
|
|
|
46
47
|
#approval: CodeAgentApprovalRequest | undefined;
|
|
47
48
|
#stopReason: CodeAgentStopReason | undefined;
|
|
48
49
|
#queue = { steering: 0, followUp: 0 };
|
|
50
|
+
#subagents: CodeAgentSubagent[] = [];
|
|
49
51
|
#openAssistant: string | undefined;
|
|
50
52
|
#openThinking: string | undefined;
|
|
51
53
|
#nextId = 0;
|
|
@@ -104,6 +106,11 @@ export class CodeTranscript {
|
|
|
104
106
|
return this.#queue;
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/** The sub-agents running right now. Not a transcript row: they are a state, and the state is the list. */
|
|
110
|
+
get subagents() {
|
|
111
|
+
return this.#subagents;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
/** Bumped on every applied event, so a view can tell "changed" from "same" without comparing arrays. */
|
|
108
115
|
get revision() {
|
|
109
116
|
return this.#revision;
|
|
@@ -202,11 +209,16 @@ export class CodeTranscript {
|
|
|
202
209
|
return;
|
|
203
210
|
case "notice":
|
|
204
211
|
return this.#notice(event.level, event.message);
|
|
212
|
+
case "subagent":
|
|
213
|
+
this.#subagents = event.agents;
|
|
214
|
+
return;
|
|
205
215
|
case "error":
|
|
206
216
|
return this.#notice("error", event.message);
|
|
207
217
|
case "idle":
|
|
208
218
|
this.#streaming = false;
|
|
209
219
|
this.#queue = { steering: 0, followUp: 0 };
|
|
220
|
+
|
|
221
|
+
this.#subagents = [];
|
|
210
222
|
this.#closeStreams(false);
|
|
211
223
|
return;
|
|
212
224
|
case "host":
|
|
@@ -21,6 +21,22 @@ export type CodeAgentApprovalPolicy = "never" | "writes" | "commands" | "all";
|
|
|
21
21
|
*/
|
|
22
22
|
export type CodeAgentInteractionMode = "await" | "suspend";
|
|
23
23
|
|
|
24
|
+
export interface CodeAgentModelInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
contextWindow?: number;
|
|
28
|
+
/** Whether this is the one the session is running on, which a listing has to mark rather than repeat. */
|
|
29
|
+
current: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One provider of the model catalogue, and whether a credential for it has been configured. */
|
|
33
|
+
export interface CodeAgentProviderInfo {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
authorized: boolean;
|
|
37
|
+
models: CodeAgentModelInfo[];
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
export interface CodeAgentMcpServerRef {
|
|
25
41
|
name: string;
|
|
26
42
|
transport: "stdio" | "http";
|
|
@@ -29,6 +45,44 @@ export interface CodeAgentMcpServerRef {
|
|
|
29
45
|
args?: string[];
|
|
30
46
|
url?: string;
|
|
31
47
|
env?: Record<string, string>;
|
|
48
|
+
/** Sent on every http request. A server behind a static token needs nothing else. */
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
/**
|
|
51
|
+
* What to use when the server answers `401` and asks for OAuth.
|
|
52
|
+
*
|
|
53
|
+
* Everything here is optional because the spec is discoverable: the challenge names the resource metadata,
|
|
54
|
+
* that names the authorization server, and most hosted servers register a client on demand. It is for the
|
|
55
|
+
* ones that do not — a provider issuing client ids by hand, or one whose scopes cannot be guessed.
|
|
56
|
+
*/
|
|
57
|
+
oauth?: { clientId?: string; clientSecret?: string; scope?: string };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Where a server stands with its credential.
|
|
62
|
+
*
|
|
63
|
+
* `required` is not a failure: it is a server that answered correctly and said who may talk to it. Reporting
|
|
64
|
+
* it as unreachable would send someone to debug a server that is working exactly as designed.
|
|
65
|
+
*/
|
|
66
|
+
export type CodeAgentMcpAuthState = "none" | "authorized" | "required";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* One declared MCP server, as the session found it.
|
|
70
|
+
*
|
|
71
|
+
* A server that answered and one that was never reachable are both here: an integration that is silently
|
|
72
|
+
* absent is indistinguishable from one the model simply chose not to use, and the tool names are the only
|
|
73
|
+
* evidence of which of the two happened.
|
|
74
|
+
*/
|
|
75
|
+
export interface CodeAgentMcpStatus {
|
|
76
|
+
name: string;
|
|
77
|
+
transport: "stdio" | "http";
|
|
78
|
+
/** The command line or the url — what tells two entries of the same shape apart without opening the file. */
|
|
79
|
+
target: string;
|
|
80
|
+
/** Published tool names, already prefixed with the server the way the model sees them. */
|
|
81
|
+
tools: string[];
|
|
82
|
+
/** Why it published none, when it published none. */
|
|
83
|
+
error?: string;
|
|
84
|
+
/** Whether a credential was needed, and whether one was held. */
|
|
85
|
+
auth?: CodeAgentMcpAuthState;
|
|
32
86
|
}
|
|
33
87
|
|
|
34
88
|
export interface CodeAgentSubagentBudget {
|
package/common/codeAgentWire.ts
CHANGED
|
@@ -154,6 +154,23 @@ export interface CodeAgentApprovalRequest {
|
|
|
154
154
|
policy: CodeAgentApprovalPolicy;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* One sub-agent the `task` tool is currently running.
|
|
159
|
+
*
|
|
160
|
+
* `tokens` is read at the moment the frame is made, so a host that draws this rail sees the child's spend
|
|
161
|
+
* climb rather than learning it only once the child is gone — which is the number that decides whether to
|
|
162
|
+
* let it keep going. A finished sub-agent leaves the list: what it did is already in the tool row it closed.
|
|
163
|
+
*/
|
|
164
|
+
export interface CodeAgentSubagent {
|
|
165
|
+
/** The `task` call that opened it, so a host can line the row up with the tool row above. */
|
|
166
|
+
id: string;
|
|
167
|
+
kind: string;
|
|
168
|
+
/** The three-to-five words the model named the task with. */
|
|
169
|
+
description: string;
|
|
170
|
+
startedAt: number;
|
|
171
|
+
tokens: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
157
174
|
export type CodeAgentEventBody =
|
|
158
175
|
| { type: "session"; info: CodeAgentSessionInfo }
|
|
159
176
|
| { type: "turn_start"; turnId: string }
|
|
@@ -180,6 +197,15 @@ export type CodeAgentEventBody =
|
|
|
180
197
|
| { type: "retry"; attempt: number; maxAttempts: number; delayMs: number; message: string }
|
|
181
198
|
| { type: "queue"; steering: string[]; followUp: string[] }
|
|
182
199
|
| { type: "notice"; level: "info" | "warning" | "error"; message: string }
|
|
200
|
+
/**
|
|
201
|
+
* Every sub-agent running right now, whole rather than as a delta.
|
|
202
|
+
*
|
|
203
|
+
* A list is re-sent on every change and on a slow tick while any child runs, so a host that missed a frame
|
|
204
|
+
* converges on the next one instead of holding a row for a child that finished. It is the one thing a turn
|
|
205
|
+
* does that the transcript cannot show: a `task` call is one tool row that stays open for minutes while its
|
|
206
|
+
* child spends tokens nobody watching sees spent.
|
|
207
|
+
*/
|
|
208
|
+
| { type: "subagent"; agents: CodeAgentSubagent[] }
|
|
183
209
|
| { type: "error"; message: string; fatal: boolean }
|
|
184
210
|
| { type: "idle" }
|
|
185
211
|
/**
|
|
@@ -236,6 +262,7 @@ export const codeAgentEventPersistence: { [key in CodeAgentEventType]: "live" |
|
|
|
236
262
|
retry: "live",
|
|
237
263
|
queue: "live",
|
|
238
264
|
notice: "live",
|
|
265
|
+
subagent: "live",
|
|
239
266
|
error: "persist",
|
|
240
267
|
idle: "live",
|
|
241
268
|
host: "live",
|
|
@@ -380,6 +407,10 @@ export const codeAgentEventLabel = (event: CodeAgentEventBody): string => {
|
|
|
380
407
|
return `queued ${event.steering.length} steering, ${event.followUp.length} follow-up`;
|
|
381
408
|
case "notice":
|
|
382
409
|
return `[${event.level}] ${event.message}`;
|
|
410
|
+
case "subagent":
|
|
411
|
+
return event.agents.length
|
|
412
|
+
? event.agents.map((agent) => `${agent.kind} · ${agent.description}`).join(" | ")
|
|
413
|
+
: "no sub-agent running";
|
|
383
414
|
case "error":
|
|
384
415
|
return `error: ${event.message}`;
|
|
385
416
|
case "idle":
|
package/common/index.ts
CHANGED
|
@@ -21,9 +21,13 @@ export {
|
|
|
21
21
|
type CodeAgentApprovalPolicy,
|
|
22
22
|
type CodeAgentBuiltinTool,
|
|
23
23
|
type CodeAgentInteractionMode,
|
|
24
|
+
type CodeAgentMcpAuthState,
|
|
24
25
|
type CodeAgentMcpServerRef,
|
|
26
|
+
type CodeAgentMcpStatus,
|
|
27
|
+
type CodeAgentModelInfo,
|
|
25
28
|
type CodeAgentPresetName,
|
|
26
29
|
type CodeAgentProfile,
|
|
30
|
+
type CodeAgentProviderInfo,
|
|
27
31
|
type CodeAgentSubagentBudget,
|
|
28
32
|
codeAgentDeniedPaths,
|
|
29
33
|
codeAgentPresets,
|
|
@@ -49,6 +53,7 @@ export {
|
|
|
49
53
|
type CodeAgentStartOptions,
|
|
50
54
|
type CodeAgentState,
|
|
51
55
|
type CodeAgentStopReason,
|
|
56
|
+
type CodeAgentSubagent,
|
|
52
57
|
type CodeAgentToolOp,
|
|
53
58
|
type CodeAgentToolOutcome,
|
|
54
59
|
type CodeAgentToolSummary,
|
|
@@ -13,9 +13,7 @@ export const PAGE_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
|
13
13
|
"default",
|
|
14
14
|
"pageConfig",
|
|
15
15
|
"head",
|
|
16
|
-
"metadata",
|
|
17
16
|
"generateHead",
|
|
18
|
-
"generateMetadata",
|
|
19
17
|
"Loading",
|
|
20
18
|
]);
|
|
21
19
|
export const LAYOUT_ROUTE_EXPORTS: ReadonlySet<string> = new Set([...PAGE_ROUTE_EXPORTS, "NotFound", "Error"]);
|
|
@@ -27,7 +25,6 @@ export const ROOT_LAYOUT_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
|
27
25
|
"reconnect",
|
|
28
26
|
"wsConnect",
|
|
29
27
|
"layoutStyle",
|
|
30
|
-
"gaTrackingId",
|
|
31
28
|
]);
|
|
32
29
|
/** Root-layout exports that are plain config rather than components, so a PascalCase check cannot allow them. */
|
|
33
30
|
export const RESERVED_ROUTE_CONFIG_EXPORTS: ReadonlySet<string> = new Set(
|
|
@@ -238,16 +238,15 @@ export class ConstantRegistry {
|
|
|
238
238
|
PurifiedModel<Model>
|
|
239
239
|
>;
|
|
240
240
|
}
|
|
241
|
-
static serialize<Value>(modelRef: Cls | Cls[], value: Value, nullable: boolean = false): Value {
|
|
241
|
+
static serialize<Value>(modelRef: Cls | Cls[], value: Value, nullable: boolean = false, of?: Cls | Cls[]): Value {
|
|
242
242
|
if (Array.isArray(value) && Array.isArray(modelRef)) {
|
|
243
243
|
const singleModelRef = modelRef.at(0);
|
|
244
244
|
if (!singleModelRef) throw new Error("No model ref found");
|
|
245
245
|
return value.map((v: object) => ConstantRegistry.serialize(singleModelRef as Cls, v)) as unknown as Value;
|
|
246
246
|
} else if (modelRef === Map && value instanceof Map) {
|
|
247
|
+
if (!of) throw new Error("A Map needs its value type (of) to serialize");
|
|
247
248
|
return Object.fromEntries(
|
|
248
|
-
[...value.entries()].map(([key,
|
|
249
|
-
return [key, ConstantRegistry.serialize(value as Cls, value)];
|
|
250
|
-
}),
|
|
249
|
+
[...value.entries()].map(([key, entry]: [string, unknown]) => [key, ConstantRegistry.serialize(of, entry)]),
|
|
251
250
|
) as unknown as Value;
|
|
252
251
|
} else if (PrimitiveRegistry.has(modelRef as Cls)) {
|
|
253
252
|
return (modelRef as typeof PrimitiveScalar)._serialize(value as PrimitiveValue) as unknown as Value;
|
|
@@ -255,17 +254,15 @@ export class ConstantRegistry {
|
|
|
255
254
|
return serialize(modelRef as ConstantCls, 0, value, "object", { nullable }) as unknown as Value;
|
|
256
255
|
} else throw new Error(`No serialize function for modelRef: ${modelRef}`);
|
|
257
256
|
}
|
|
258
|
-
static deserialize<Value>(modelRef: Cls | Cls[], value: Value, nullable: boolean = false): Value {
|
|
257
|
+
static deserialize<Value>(modelRef: Cls | Cls[], value: Value, nullable: boolean = false, of?: Cls | Cls[]): Value {
|
|
259
258
|
if (Array.isArray(value) && Array.isArray(modelRef)) {
|
|
260
259
|
const singleModelRef = modelRef.at(0);
|
|
261
260
|
if (!singleModelRef) throw new Error("No model ref found");
|
|
262
261
|
return value.map((v: object) => ConstantRegistry.deserialize(singleModelRef as Cls, v)) as unknown as Value;
|
|
263
|
-
} else if (modelRef === Map && value
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}),
|
|
268
|
-
) as unknown as Value;
|
|
262
|
+
} else if (modelRef === Map && value !== null && typeof value === "object") {
|
|
263
|
+
if (!of) throw new Error("A Map needs its value type (of) to deserialize");
|
|
264
|
+
const entries: [string, unknown][] = value instanceof Map ? [...value.entries()] : Object.entries(value);
|
|
265
|
+
return new Map(entries.map(([key, entry]) => [key, ConstantRegistry.deserialize(of, entry)])) as unknown as Value;
|
|
269
266
|
} else if (PrimitiveRegistry.has(modelRef as Cls)) {
|
|
270
267
|
return (modelRef as typeof PrimitiveScalar)._parse(value as PrimitiveValue) as unknown as Value;
|
|
271
268
|
} else if (ConstantRegistry.modelRefNameMap.has(modelRef as Cls)) {
|
package/document/dataLoader.ts
CHANGED
|
@@ -11,7 +11,12 @@ type BatchLoadFn<Key, Value> = (
|
|
|
11
11
|
) => PromiseLike<ReadonlyArray<Value | Error>> | ReadonlyArray<Value | Error>;
|
|
12
12
|
|
|
13
13
|
interface DataLoaderOptions<Key, CacheKey> {
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* How long a loaded key is answered from memory: `false` (the default) remembers nothing past its own batch,
|
|
16
|
+
* a number keeps each key for that many milliseconds, and `true` keeps it for the life of the loader. A model's
|
|
17
|
+
* loaders live as long as the process, so anything but `false` serves a stale document after it changes.
|
|
18
|
+
*/
|
|
19
|
+
cache?: boolean | number;
|
|
15
20
|
cacheKeyFn?: (key: Key) => CacheKey;
|
|
16
21
|
batch?: boolean;
|
|
17
22
|
batchScheduleFn?: (callback: () => void) => void;
|
|
@@ -27,20 +32,27 @@ interface BatchItem<Key, Value> {
|
|
|
27
32
|
|
|
28
33
|
/** Minimal DataLoader-compatible batch loader used by Akan document resolvers. */
|
|
29
34
|
export class DataLoader<Key, Value, CacheKey = Key> {
|
|
35
|
+
static readonly #minSweepSize = 1024;
|
|
30
36
|
readonly name?: string;
|
|
31
37
|
readonly #batchLoadFn: BatchLoadFn<Key, Value>;
|
|
32
|
-
readonly #
|
|
38
|
+
readonly #ttl: number;
|
|
33
39
|
readonly #cacheKeyFn: (key: Key) => CacheKey;
|
|
34
40
|
readonly #batch: boolean;
|
|
35
41
|
readonly #batchScheduleFn: (callback: () => void) => void;
|
|
36
42
|
readonly #maxBatchSize: number;
|
|
37
|
-
readonly #promiseCache = new Map<CacheKey, Promise<Value
|
|
43
|
+
readonly #promiseCache = new Map<CacheKey, { promise: Promise<Value>; expiresAt: number }>();
|
|
44
|
+
#sweepAt = DataLoader.#minSweepSize;
|
|
38
45
|
#queue: BatchItem<Key, Value>[] = [];
|
|
39
46
|
#scheduled = false;
|
|
40
47
|
|
|
41
48
|
constructor(batchLoadFn: BatchLoadFn<Key, Value>, options: DataLoaderOptions<Key, CacheKey> = {}) {
|
|
42
49
|
this.#batchLoadFn = batchLoadFn;
|
|
43
|
-
this.#
|
|
50
|
+
this.#ttl =
|
|
51
|
+
options.cache === true
|
|
52
|
+
? Number.POSITIVE_INFINITY
|
|
53
|
+
: typeof options.cache === "number" && options.cache > 0
|
|
54
|
+
? options.cache
|
|
55
|
+
: 0;
|
|
44
56
|
this.#cacheKeyFn = options.cacheKeyFn ?? ((key) => key as unknown as CacheKey);
|
|
45
57
|
this.#batch = options.batch !== false;
|
|
46
58
|
this.#batchScheduleFn = options.batchScheduleFn ?? ((callback) => queueMicrotask(callback));
|
|
@@ -50,17 +62,21 @@ export class DataLoader<Key, Value, CacheKey = Key> {
|
|
|
50
62
|
|
|
51
63
|
load(key: Key): Promise<Value> {
|
|
52
64
|
const cacheKey = this.#cacheKeyFn(key);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (cached) return cached;
|
|
56
|
-
}
|
|
65
|
+
const cached = this.#cached(cacheKey);
|
|
66
|
+
if (cached) return cached;
|
|
57
67
|
|
|
58
68
|
const promise = new Promise<Value>((resolve, reject) => {
|
|
59
69
|
this.#queue.push({ key, resolve, reject });
|
|
60
70
|
if (this.#batch) this.#schedule();
|
|
61
71
|
else this.#dispatch();
|
|
62
72
|
});
|
|
63
|
-
if (this.#
|
|
73
|
+
if (this.#ttl > 0) {
|
|
74
|
+
this.#remember(cacheKey, promise);
|
|
75
|
+
|
|
76
|
+
promise.catch(() => {
|
|
77
|
+
if (this.#promiseCache.get(cacheKey)?.promise === promise) this.#promiseCache.delete(cacheKey);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
64
80
|
return promise;
|
|
65
81
|
}
|
|
66
82
|
|
|
@@ -80,13 +96,30 @@ export class DataLoader<Key, Value, CacheKey = Key> {
|
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
prime(key: Key, value: Value | Error): this {
|
|
83
|
-
if (
|
|
99
|
+
if (this.#ttl <= 0) return this;
|
|
84
100
|
const cacheKey = this.#cacheKeyFn(key);
|
|
85
|
-
if (this.#
|
|
86
|
-
this.#
|
|
101
|
+
if (this.#cached(cacheKey)) return this;
|
|
102
|
+
this.#remember(cacheKey, value instanceof Error ? Promise.reject(value) : Promise.resolve(value));
|
|
87
103
|
return this;
|
|
88
104
|
}
|
|
89
105
|
|
|
106
|
+
#cached(cacheKey: CacheKey): Promise<Value> | undefined {
|
|
107
|
+
if (this.#ttl <= 0) return undefined;
|
|
108
|
+
const entry = this.#promiseCache.get(cacheKey);
|
|
109
|
+
if (!entry) return undefined;
|
|
110
|
+
if (entry.expiresAt > Date.now()) return entry.promise;
|
|
111
|
+
this.#promiseCache.delete(cacheKey);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#remember(cacheKey: CacheKey, promise: Promise<Value>) {
|
|
116
|
+
this.#promiseCache.set(cacheKey, { promise, expiresAt: Date.now() + this.#ttl });
|
|
117
|
+
if (this.#promiseCache.size < this.#sweepAt) return;
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
for (const [key, entry] of this.#promiseCache) if (entry.expiresAt <= now) this.#promiseCache.delete(key);
|
|
120
|
+
this.#sweepAt = Math.max(DataLoader.#minSweepSize, this.#promiseCache.size * 2);
|
|
121
|
+
}
|
|
122
|
+
|
|
90
123
|
#schedule() {
|
|
91
124
|
if (this.#scheduled) return;
|
|
92
125
|
this.#scheduled = true;
|
|
@@ -159,7 +192,7 @@ export const createLoader = <Key, Value>(model: LoaderModel, fieldName = "id", d
|
|
|
159
192
|
});
|
|
160
193
|
return data as unknown as Promise<Value[]>;
|
|
161
194
|
},
|
|
162
|
-
{ name: "dataloader"
|
|
195
|
+
{ name: "dataloader" },
|
|
163
196
|
);
|
|
164
197
|
};
|
|
165
198
|
export const createArrayLoader = <K, V>(model: LoaderModel, fieldName = "id", defaultQuery: QueryOf<unknown> = {}) => {
|
|
@@ -194,7 +227,7 @@ export const createArrayElementLoader = <K, V>(
|
|
|
194
227
|
});
|
|
195
228
|
return data as unknown as Promise<V[]>;
|
|
196
229
|
},
|
|
197
|
-
{ name: "dataloader"
|
|
230
|
+
{ name: "dataloader" },
|
|
198
231
|
);
|
|
199
232
|
};
|
|
200
233
|
|
|
@@ -214,7 +247,7 @@ export const createQueryLoader = <Key, Value>(
|
|
|
214
247
|
});
|
|
215
248
|
return data as unknown as Promise<Value[]>;
|
|
216
249
|
},
|
|
217
|
-
{ name: "dataloader"
|
|
250
|
+
{ name: "dataloader" },
|
|
218
251
|
);
|
|
219
252
|
};
|
|
220
253
|
|
package/document/loaderInfo.ts
CHANGED
|
@@ -15,25 +15,46 @@ export type ModelCls<
|
|
|
15
15
|
_libsOnSchema: (schema: SchemaOf<any, any>) => void;
|
|
16
16
|
}
|
|
17
17
|
>;
|
|
18
|
+
export interface LoaderOption {
|
|
19
|
+
/** `false` by default; a number of milliseconds, or `true` for as long as the process runs. */
|
|
20
|
+
cache?: boolean | number;
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
export class LoaderInfo<Doc = any, Key extends keyof Doc = keyof Doc, QueryArg = Doc[Key]> {
|
|
19
24
|
type: LoaderType;
|
|
20
25
|
field: Key | Key[];
|
|
21
26
|
defaultQuery: QueryOf<unknown>;
|
|
27
|
+
cache: boolean | number;
|
|
22
28
|
queryArg: QueryArg | undefined;
|
|
23
|
-
constructor(
|
|
29
|
+
constructor(
|
|
30
|
+
type: LoaderType,
|
|
31
|
+
field: Key | Key[],
|
|
32
|
+
defaultQuery: QueryOf<unknown> = {},
|
|
33
|
+
{ cache = false }: LoaderOption = {},
|
|
34
|
+
) {
|
|
24
35
|
this.type = type;
|
|
25
36
|
this.field = field;
|
|
26
37
|
this.defaultQuery = defaultQuery;
|
|
38
|
+
this.cache = cache;
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
export const makeLoaderBuilder = <Doc>() => ({
|
|
31
|
-
byField: <Key extends keyof Doc & string>(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
byField: <Key extends keyof Doc & string>(
|
|
44
|
+
fieldName: Key,
|
|
45
|
+
defaultQuery: QueryOf<unknown> = {},
|
|
46
|
+
option: LoaderOption = {},
|
|
47
|
+
) => new LoaderInfo<Doc, Key>("field", fieldName, defaultQuery, option),
|
|
48
|
+
byArrayField: <Key extends keyof Doc & string>(
|
|
49
|
+
fieldName: Key,
|
|
50
|
+
defaultQuery: QueryOf<unknown> = {},
|
|
51
|
+
option: LoaderOption = {},
|
|
52
|
+
) => new LoaderInfo<Doc, Key>("arrayField", fieldName, defaultQuery, option),
|
|
53
|
+
byQuery: <Key extends keyof Doc & string>(
|
|
54
|
+
queryKeys: readonly Key[],
|
|
55
|
+
defaultQuery: QueryOf<unknown> = {},
|
|
56
|
+
option: LoaderOption = {},
|
|
57
|
+
) => new LoaderInfo<Doc, Key, Pick<Doc, Key>>("query", queryKeys as Key[], defaultQuery, option),
|
|
37
58
|
});
|
|
38
59
|
|
|
39
60
|
export type LoaderBuilder<Doc = any> = (builder: ReturnType<typeof makeLoaderBuilder<Doc>>) => {
|
|
@@ -544,7 +544,7 @@ export class FetchClient {
|
|
|
544
544
|
|
|
545
545
|
const anyCruGuards = signal.cruGuards ?? signal.createGuards ?? signal.updateGuards ?? signal.removeGuards;
|
|
546
546
|
const updateGuards = signal.updateGuards ?? signal.cruGuards;
|
|
547
|
-
if (
|
|
547
|
+
if (signal.getGuards) {
|
|
548
548
|
this.#setHandlerFactory(names.viewModel, () =>
|
|
549
549
|
this.#makeModelHandleFn(refName, names.model, names.viewModel, `${refName}View`),
|
|
550
550
|
);
|
|
@@ -557,6 +557,8 @@ export class FetchClient {
|
|
|
557
557
|
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
558
558
|
}) as FetchHandler,
|
|
559
559
|
);
|
|
560
|
+
}
|
|
561
|
+
if (signal.getGuards && anyCruGuards) {
|
|
560
562
|
this.#setHandlerFactory(names.editModel, () =>
|
|
561
563
|
this.#makeModelHandleFn(refName, names.model, names.editModel, `${refName}Edit`),
|
|
562
564
|
);
|
|
@@ -569,17 +571,17 @@ export class FetchClient {
|
|
|
569
571
|
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
570
572
|
}) as FetchHandler,
|
|
571
573
|
);
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
574
|
+
}
|
|
575
|
+
if (updateGuards) {
|
|
576
|
+
this.#setHandlerFactory(
|
|
577
|
+
names.mergeModel,
|
|
578
|
+
() =>
|
|
579
|
+
(async (modelOrId: string | { id: string }, data: UnknownRecord, option?: FetchPolicy) => {
|
|
580
|
+
const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
|
|
581
|
+
const updateFn = this.#requireHandler(names.updateModel, names.mergeModel);
|
|
582
|
+
return await updateFn(id, data, option);
|
|
583
|
+
}) as FetchHandler,
|
|
584
|
+
);
|
|
583
585
|
}
|
|
584
586
|
|
|
585
587
|
this.#setHandlerFactory(
|