akanjs 3.0.0-alpha.54 → 3.0.0-alpha.56

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.
Files changed (45) hide show
  1. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  2. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  3. package/package.json +1 -1
  4. package/server/rscWorker.tsx +5 -13
  5. package/server/webRouter.ts +3 -5
  6. package/service/agent.service.ts +5 -2
  7. package/service/toolNames.ts +119 -0
  8. package/store/agent/StoreSurfaceSource.ts +9 -1
  9. package/types/service/toolNames.d.ts +35 -0
  10. package/types/store/agent/StoreSurfaceSource.d.ts +9 -1
  11. package/types/ui/Agent/Chat.d.ts +25 -4
  12. package/types/ui/Agent/Context.d.ts +6 -2
  13. package/types/ui/Agent/Zone.d.ts +27 -4
  14. package/types/ui/Agent/agentSessionOf.d.ts +12 -3
  15. package/types/ui/Agent/index.d.ts +1 -1
  16. package/types/ui/Agent/sessionHistory.d.ts +1 -1
  17. package/types/ui/Agent/sessionView.d.ts +15 -0
  18. package/types/ui/Field.d.ts +12 -12
  19. package/types/ui/RecentTime.d.ts +18 -1
  20. package/types/ui/ToggleSelect.d.ts +1 -0
  21. package/types/ui/index.d.ts +5 -3
  22. package/types/vendor/use-agentic/AgentProvider.d.ts +1 -2
  23. package/types/vendor/use-agentic/AgentSession.d.ts +33 -6
  24. package/types/vendor/use-agentic/AgenticSurface.d.ts +5 -0
  25. package/types/vendor/use-agentic/index.d.ts +1 -0
  26. package/types/vendor/use-agentic/sharedContext.d.ts +15 -0
  27. package/ui/Agent/Chat.tsx +76 -36
  28. package/ui/Agent/Context.tsx +13 -4
  29. package/ui/Agent/Zone.tsx +48 -12
  30. package/ui/Agent/agentSessionOf.ts +23 -4
  31. package/ui/Agent/sessionHistory.ts +10 -3
  32. package/ui/Agent/sessionView.ts +38 -0
  33. package/ui/Field.tsx +41 -34
  34. package/ui/RecentTime.tsx +83 -35
  35. package/ui/System/Client.tsx +19 -7
  36. package/ui/System/ThemeToggle.tsx +14 -5
  37. package/ui/ToggleSelect.tsx +4 -1
  38. package/ui/index.ts +29 -3
  39. package/vendor/use-agentic/AgentProvider.tsx +1 -1
  40. package/vendor/use-agentic/AgentSession.ts +84 -19
  41. package/vendor/use-agentic/AgenticSurface.ts +9 -0
  42. package/vendor/use-agentic/index.ts +1 -0
  43. package/vendor/use-agentic/sharedContext.ts +25 -0
  44. package/vendor/use-agentic/surfaceContext.ts +4 -3
  45. package/vendor/use-agentic/useAgent.ts +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.54",
3
+ "version": "3.0.0-alpha.56",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -547,7 +547,6 @@ export class RscRenderer {
547
547
  });
548
548
  return;
549
549
  }
550
- const theme = untrackedCookies().get("theme")?.value;
551
550
  let element: ReactNode;
552
551
  let effectivePatchDecision = safePatchDecision;
553
552
  if (match && safePatchDecision.status === "patch" && safePatchDecision.patch) {
@@ -563,9 +562,9 @@ export class RscRenderer {
563
562
  reason: "suffix-compose-fallback",
564
563
  commonPrefixLength: safePatchDecision.commonPrefixLength,
565
564
  };
566
- element = await this.#renderMatched(urlObj, match, theme, searchParams);
565
+ element = await this.#renderMatched(urlObj, match, searchParams);
567
566
  } else element = suffixElement;
568
- } else if (match) element = await this.#renderMatched(urlObj, match, theme, searchParams);
567
+ } else if (match) element = await this.#renderMatched(urlObj, match, searchParams);
569
568
  else element = await this.#renderNotFound(urlObj);
570
569
  const traceCacheKey =
571
570
  effectivePatchDecision.status === "patch" ? (patchCacheEntry?.key ?? cacheEntry?.key) : cacheEntry?.key;
@@ -1236,12 +1235,8 @@ export class RscRenderer {
1236
1235
  const routeHeadSnapshot = this.#createRouteHeadSnapshot(url, routeHead, {
1237
1236
  hasExplicitLanguageAlternates: routeHead.hasExplicitLanguageAlternates,
1238
1237
  });
1239
- const theme = untrackedCookies().get("theme")?.value;
1240
1238
  return (
1241
- <html
1242
- lang={params.lang ?? RscRenderer.#getLocale(pathname, this.#i18n)}
1243
- {...(theme ? { "data-theme": theme } : { suppressHydrationWarning: true })}
1244
- >
1239
+ <html lang={params.lang ?? RscRenderer.#getLocale(pathname, this.#i18n)} suppressHydrationWarning>
1245
1240
  <head key="head">
1246
1241
  <meta key="charset" charSet="utf-8" />
1247
1242
  <meta key="viewport" name="viewport" content="width=device-width, initial-scale=1" />
@@ -1263,7 +1258,6 @@ export class RscRenderer {
1263
1258
  async #renderMatched(
1264
1259
  url: URL,
1265
1260
  match: { pathRoute: PathRoute; params: Record<string, string> },
1266
- theme?: string,
1267
1261
  searchParams = RouteTreeBuilder.parseSearchParams(url.search),
1268
1262
  ): Promise<ReactNode> {
1269
1263
  this.#logger.verbose(
@@ -1289,11 +1283,9 @@ export class RscRenderer {
1289
1283
  searchParams,
1290
1284
  navKey: url.pathname + url.search,
1291
1285
  });
1286
+
1292
1287
  return (
1293
- <html
1294
- lang={match.params.lang ?? this.#i18n.defaultLocale}
1295
- {...(theme ? { "data-theme": theme } : { suppressHydrationWarning: true })}
1296
- >
1288
+ <html lang={match.params.lang ?? this.#i18n.defaultLocale} suppressHydrationWarning>
1297
1289
  <head key="head">
1298
1290
  <meta key="charset" charSet="utf-8" />
1299
1291
  <meta key="viewport" name="viewport" content="width=device-width, initial-scale=1" />
@@ -610,7 +610,8 @@ export class WebRouter {
610
610
  if (rscResult.type === "redirect")
611
611
  return Response.redirect(new URL(rscResult.location, url.origin), rscResult.status);
612
612
  if (rscResult.type === "not-found") return this.#renderSystemNotFoundFallbackResponse(req, url);
613
- const themeCookieExists = WebRouter.#hasCookie(req, "theme");
613
+
614
+ const cookieTheme = WebRouter.#cookieValue(req, "theme");
614
615
  const hostRequestStore = createRequestStore(req);
615
616
  const extraBootstrapInline = [
616
617
  rscResult.trace?.routeState
@@ -628,7 +629,7 @@ export class WebRouter {
628
629
  bootstrapModules: [this.#artifact.rscClientUrl],
629
630
  extraBootstrapInline: extraBootstrapInline || undefined,
630
631
  importmap: this.#artifact.vendorMap,
631
- theme: themeCookieExists ? undefined : (rscResult.theme ?? "system"),
632
+ theme: cookieTheme ?? rscResult.theme ?? "system",
632
633
  lateControl: rscResult.lateControl,
633
634
  waitForAllReady: rscResult.trace?.ssrBlocking ?? false,
634
635
  onCancel: (reason: unknown) => {
@@ -797,9 +798,6 @@ export class WebRouter {
797
798
  }
798
799
  }
799
800
 
800
- static #hasCookie(req: Request, name: string): boolean {
801
- return parseCookieHeader(req.headers.get("cookie") ?? "").has(name);
802
- }
803
801
  #getHtmlCacheEntry(req: Request, url: URL): { entry: RouteCacheEntry | null; reason?: string } {
804
802
  const decision = resolvePublicRouteCacheEntryDecision({
805
803
  request: req,
@@ -7,15 +7,18 @@ import type {
7
7
  } from "./predefinedAdaptor/llm.adaptor";
8
8
  import { LlmAdaptorRole } from "./predefinedAdaptor/role.adaptor";
9
9
  import { serve } from "./serve";
10
+ import { ToolNames } from "./toolNames";
10
11
 
11
12
  export class AgentService extends serve("agent" as const, ({ plug }) => ({
12
13
  llm: plug(LlmAdaptorRole),
13
14
  })) {
14
15
  async runTurn(request: LlmTurnRequest, onDelta?: (delta: string) => void) {
15
- const prepared = AgentService.readable(AgentService.explained(request), this.llm.accepts);
16
+
17
+ const names = ToolNames.of(request);
18
+ const prepared = names.encode(AgentService.readable(AgentService.explained(request), this.llm.accepts));
16
19
  const answer = await this.llm.chat(prepared, onDelta);
17
20
  if (!answer) throw new Err("agent.error.llmUnavailable");
18
- return { text: answer.text ?? "", toolCalls: answer.toolCalls ?? [], stop: answer.stop };
21
+ return { text: answer.text ?? "", toolCalls: names.decode(answer.toolCalls ?? []), stop: answer.stop };
19
22
  }
20
23
 
21
24
  /**
@@ -0,0 +1,119 @@
1
+ import type { AgentWireMessage, AgentWireToolCall, LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
2
+
3
+ const wireSafe = /^[A-Za-z0-9_-]+$/;
4
+
5
+ /**
6
+ * Renames a turn's tools onto what a provider's function-calling wire accepts, and reads the answer back.
7
+ *
8
+ * A zone publishes its tools scope-prefixed — `videoProjectDraft.createVideoProject` — which is legal for MCP,
9
+ * where `.` is an allowed character and the scope join. Every OpenAI-compatible and Anthropic function schema is
10
+ * narrower: `[A-Za-z0-9_-]`, at most 64 characters. A provider that validates answers 400; DeepSeek does not, and
11
+ * what happened instead was worse to debug — the model normalized the illegal name itself, called the bare
12
+ * `createVideoProject`, and the browser answered `Unknown tool`, spending a turn on a tool that was published all
13
+ * along.
14
+ *
15
+ * Renamed here rather than in each adaptor for the reason `AgentService.explained` gives: every adaptor would
16
+ * otherwise have to remember, and forgetting is silent. A name the wire already accepts is left alone, so the
17
+ * root agent's request is byte-for-byte what it was.
18
+ */
19
+ export class ToolNames {
20
+ /** Both dialects reject a longer name, and neither says so in terms of the tool you wrote. */
21
+ static readonly limit = 64;
22
+
23
+ readonly #toWire = new Map<string, string>();
24
+ readonly #toSurface = new Map<string, string>();
25
+
26
+ /**
27
+ * Every name the request carries, not just the published ones: the transcript holds calls to tools that have
28
+ * since left the screen, and one of those reaching the wire unrenamed is the same failure a turn later.
29
+ */
30
+ static of(request: LlmTurnRequest): ToolNames {
31
+ return new ToolNames([
32
+ ...request.tools.map((tool) => tool.name),
33
+ ...request.messages.flatMap((message) => ToolNames.#namesIn(message)),
34
+ ]);
35
+ }
36
+
37
+ constructor(names: Iterable<string>) {
38
+ const all = [...new Set(names)];
39
+
40
+ const taken = new Set(all.filter((name) => ToolNames.#fits(name)));
41
+
42
+ for (const name of all.filter((candidate) => !ToolNames.#fits(candidate)).sort((a, b) => (a < b ? -1 : 1))) {
43
+ const wire = ToolNames.#unique(ToolNames.#fold(name), taken);
44
+ taken.add(wire);
45
+ this.#toWire.set(name, wire);
46
+ this.#toSurface.set(wire, name);
47
+ }
48
+ }
49
+
50
+ get renamed() {
51
+ return this.#toWire.size > 0;
52
+ }
53
+
54
+ wire(name: string) {
55
+ return this.#toWire.get(name) ?? name;
56
+ }
57
+
58
+ /**
59
+ * Unknown stays as it came. A model that invented a name is answered by the surface's own `Unknown tool`, which
60
+ * lands in the transcript as a tool result it can correct from — guessing which tool it meant would run one.
61
+ */
62
+ surface(name: string) {
63
+ return this.#toSurface.get(name) ?? name;
64
+ }
65
+
66
+ encode(request: LlmTurnRequest): LlmTurnRequest {
67
+ if (!this.renamed) return request;
68
+ return {
69
+ ...request,
70
+ tools: request.tools.map((tool) => ({ ...tool, name: this.wire(tool.name) })),
71
+ messages: request.messages.map((message) => this.#encoded(message)),
72
+ };
73
+ }
74
+
75
+ decode(calls: AgentWireToolCall[]): AgentWireToolCall[] {
76
+ if (!this.renamed) return calls;
77
+ return calls.map((call) => ({ ...call, name: this.surface(call.name) }));
78
+ }
79
+
80
+ #encoded(message: AgentWireMessage): AgentWireMessage {
81
+ if (!message.toolCalls?.length && !message.toolResults?.length) return message;
82
+ return {
83
+ ...message,
84
+ ...(message.toolCalls?.length
85
+ ? { toolCalls: message.toolCalls.map((call) => ({ ...call, name: this.wire(call.name) })) }
86
+ : {}),
87
+ ...(message.toolResults?.length
88
+ ? { toolResults: message.toolResults.map((result) => ({ ...result, name: this.wire(result.name) })) }
89
+ : {}),
90
+ };
91
+ }
92
+
93
+ static #namesIn(message: AgentWireMessage): string[] {
94
+ return [
95
+ ...(message.toolCalls ?? []).map((call) => call.name),
96
+ ...(message.toolResults ?? []).map((result) => result.name),
97
+ ];
98
+ }
99
+
100
+ static #fits(name: string) {
101
+ return name.length <= ToolNames.limit && wireSafe.test(name);
102
+ }
103
+
104
+ /** `.` is the one character the surface itself adds, so it folds to the `__` every MCP client already reads. */
105
+ static #fold(name: string) {
106
+ const folded = name.replaceAll(".", "__").replace(/[^A-Za-z0-9_-]/g, "-");
107
+
108
+ return folded.length <= ToolNames.limit ? folded : folded.slice(folded.length - ToolNames.limit);
109
+ }
110
+
111
+ static #unique(candidate: string, taken: Set<string>) {
112
+ if (!taken.has(candidate)) return candidate;
113
+ for (let idx = 2; ; idx += 1) {
114
+ const suffix = `_${idx}`;
115
+ const next = `${candidate.slice(0, ToolNames.limit - suffix.length)}${suffix}`;
116
+ if (!taken.has(next)) return next;
117
+ }
118
+ }
119
+ }
@@ -13,13 +13,21 @@ import { ScreenTarget } from "./ScreenTarget";
13
13
  *
14
14
  * Per zone view: a zone's `readState` reaches the keys its own subtree subscribes, and its `readScreen` and
15
15
  * `highlight` reach into its own `data-agent-zone` container rather than the whole document. A page can shadow any
16
- * of them by registering a hook tool of the same name — hook entries win over a source's.
16
+ * of them by registering a hook tool of the same name — hook entries win over a source's. **That is a root-scope
17
+ * move only**: inside a zone the hook entry is registered under its scope-prefixed name, which can never collide
18
+ * with the bare name a source publishes, so a zone drops a built-in with the session's `builtins` option instead.
17
19
  */
18
20
  export class StoreSurfaceSource implements SurfaceSource {
19
21
  /** Defined in `akanjs/ui/styles.css`, so the flash follows the app's own theme tokens. */
20
22
  static readonly highlightClass = "akan-agent-highlight";
21
23
  /** Mirrors the animation in that stylesheet: the class outlives the ring by nothing. */
22
24
  static readonly highlightMs = 2400;
25
+ /**
26
+ * What `tools()` contributes, in the order it builds them — the list a session's `builtins` option selects from.
27
+ * A screen that declares a hook tool of one of these names is not in it: that entry is the screen's, not this
28
+ * source's, so withholding the built-ins never withholds a tool a component published on purpose.
29
+ */
30
+ static readonly builtins = ["navigate", "goBack", "readScreen", "readState", "highlight"] as const;
23
31
 
24
32
  #bridge: AgentBridge | null;
25
33
  readonly #builtins = new Map<string, ToolEntry[]>();
@@ -0,0 +1,35 @@
1
+ import type { AgentWireToolCall, LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
2
+ /**
3
+ * Renames a turn's tools onto what a provider's function-calling wire accepts, and reads the answer back.
4
+ *
5
+ * A zone publishes its tools scope-prefixed — `videoProjectDraft.createVideoProject` — which is legal for MCP,
6
+ * where `.` is an allowed character and the scope join. Every OpenAI-compatible and Anthropic function schema is
7
+ * narrower: `[A-Za-z0-9_-]`, at most 64 characters. A provider that validates answers 400; DeepSeek does not, and
8
+ * what happened instead was worse to debug — the model normalized the illegal name itself, called the bare
9
+ * `createVideoProject`, and the browser answered `Unknown tool`, spending a turn on a tool that was published all
10
+ * along.
11
+ *
12
+ * Renamed here rather than in each adaptor for the reason `AgentService.explained` gives: every adaptor would
13
+ * otherwise have to remember, and forgetting is silent. A name the wire already accepts is left alone, so the
14
+ * root agent's request is byte-for-byte what it was.
15
+ */
16
+ export declare class ToolNames {
17
+ #private;
18
+ /** Both dialects reject a longer name, and neither says so in terms of the tool you wrote. */
19
+ static readonly limit = 64;
20
+ /**
21
+ * Every name the request carries, not just the published ones: the transcript holds calls to tools that have
22
+ * since left the screen, and one of those reaching the wire unrenamed is the same failure a turn later.
23
+ */
24
+ static of(request: LlmTurnRequest): ToolNames;
25
+ constructor(names: Iterable<string>);
26
+ get renamed(): boolean;
27
+ wire(name: string): string;
28
+ /**
29
+ * Unknown stays as it came. A model that invented a name is answered by the surface's own `Unknown tool`, which
30
+ * lands in the transcript as a tool result it can correct from — guessing which tool it meant would run one.
31
+ */
32
+ surface(name: string): string;
33
+ encode(request: LlmTurnRequest): LlmTurnRequest;
34
+ decode(calls: AgentWireToolCall[]): AgentWireToolCall[];
35
+ }
@@ -8,7 +8,9 @@ import { AgentBridge } from "./AgentBridge.d.ts";
8
8
  *
9
9
  * Per zone view: a zone's `readState` reaches the keys its own subtree subscribes, and its `readScreen` and
10
10
  * `highlight` reach into its own `data-agent-zone` container rather than the whole document. A page can shadow any
11
- * of them by registering a hook tool of the same name — hook entries win over a source's.
11
+ * of them by registering a hook tool of the same name — hook entries win over a source's. **That is a root-scope
12
+ * move only**: inside a zone the hook entry is registered under its scope-prefixed name, which can never collide
13
+ * with the bare name a source publishes, so a zone drops a built-in with the session's `builtins` option instead.
12
14
  */
13
15
  export declare class StoreSurfaceSource implements SurfaceSource {
14
16
  #private;
@@ -16,6 +18,12 @@ export declare class StoreSurfaceSource implements SurfaceSource {
16
18
  static readonly highlightClass = "akan-agent-highlight";
17
19
  /** Mirrors the animation in that stylesheet: the class outlives the ring by nothing. */
18
20
  static readonly highlightMs = 2400;
21
+ /**
22
+ * What `tools()` contributes, in the order it builds them — the list a session's `builtins` option selects from.
23
+ * A screen that declares a hook tool of one of these names is not in it: that entry is the screen's, not this
24
+ * source's, so withholding the built-ins never withholds a tool a component published on purpose.
25
+ */
26
+ static readonly builtins: readonly ["navigate", "goBack", "readScreen", "readState", "highlight"];
19
27
  /** Lazy by default: `AgentBridge.of()` walks the whole store, so it waits for the first enumeration. */
20
28
  constructor(bridge?: AgentBridge);
21
29
  tools: (view?: string[]) => ToolEntry[];
@@ -1,7 +1,8 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AgentRunner, type CompactOptions } from "../../vendor/use-agentic.d.ts";
2
+ import { type AgentRunner, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
3
3
  import type { AttachReader } from "./attachment.d.ts";
4
4
  import type { PersistOption } from "./sessionHistory.d.ts";
5
+ import type { BuiltinOption } from "./sessionView.d.ts";
5
6
  import type { VoiceEngine } from "./voice.d.ts";
6
7
  export interface ChatProps {
7
8
  /** Reaches whichever surface is showing — the launcher while closed, the panel while open. */
@@ -17,6 +18,13 @@ export interface ChatProps {
17
18
  * messages left verbatim below the summary. Tune it per provider; `{ at: 0 }` turns it off.
18
19
  */
19
20
  compact?: CompactOptions;
21
+ /**
22
+ * Which of the runtime's own tools this chat's agent gets — all of them by default, `false` none, an array
23
+ * exactly the ones it names. A chat that must not leave the screen it is on drops `navigate` and `goBack`.
24
+ */
25
+ builtins?: BuiltinOption;
26
+ /** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
27
+ onCompact?: AgentSessionOptions["onCompact"];
20
28
  defaultOpen?: boolean;
21
29
  /**
22
30
  * Controlled open state. Pass it with `onOpenChange` to drive the panel from the app's own control — a header
@@ -26,8 +34,14 @@ export interface ChatProps {
26
34
  onOpenChange?: (open: boolean) => void;
27
35
  /** `false` draws no launcher, for an app that opens the panel from a control of its own. */
28
36
  launcher?: boolean;
29
- /** Keeps the transcript across reloads — sessionStorage by default, `{ storage: "local" }` to outlive the tab. */
30
- persist?: PersistOption;
37
+ /**
38
+ * Keeps the transcript across reloads — sessionStorage by default, `{ storage: "local" }` to outlive the tab, or
39
+ * a `SessionHistory` of the app's own to keep it anywhere else, a server included.
40
+ *
41
+ * Ignored, like every session option above it, when an enclosing `Agent.Zone` or `AgentProvider` already holds a
42
+ * session: this chat then binds to that one, and the options belong to whoever built it.
43
+ */
44
+ persist?: PersistOption | SessionHistory;
31
45
  /** Renders in the page flow instead of floating above it — a zone chat that lives inside its own section. */
32
46
  inline?: boolean;
33
47
  /** `false` gives the browser its own Cmd/Ctrl+L back, for an app whose shell already spends that chord. */
@@ -39,6 +53,13 @@ export interface ChatProps {
39
53
  intro?: ReactNode;
40
54
  /** Extra header controls, left of the built-in clear and close buttons. */
41
55
  header?: ReactNode;
56
+ /**
57
+ * `false` draws no header bar at all — for an `inline` chat inside a panel the app already titles. The extra
58
+ * `header` controls go with it, and the clear action stays reachable as the `/new` command.
59
+ */
60
+ chrome?: boolean;
61
+ /** The composer's opening text, read once at mount — where a `?prompt=` lands without sending it. */
62
+ defaultDraft?: string;
42
63
  /**
43
64
  * Reads a file the user attached into an attachment, or answers `null` to leave it to the built-in reader
44
65
  * (images as bytes, text as text). This is where an app puts what needs a parser — a PDF's text, a spreadsheet's
@@ -60,6 +81,6 @@ export interface ChatProps {
60
81
  * `persist` keeps it. An enclosing AgentProvider's session wins, which is how an app isolates a surface or swaps
61
82
  * the loop while keeping this UI.
62
83
  */
63
- export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, compact, defaultOpen, open: openProp, onOpenChange, launcher, persist, inline, shortcut, launcherClassName, panelClassName, intro, header, attach, voice, }: ChatProps) => ReactNode;
84
+ export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, compact, builtins, onCompact, defaultOpen, open: openProp, onOpenChange, launcher, persist, inline, shortcut, launcherClassName, panelClassName, intro, header, chrome, defaultDraft, attach, voice, }: ChatProps) => ReactNode;
64
85
  declare const _default: import("react").ComponentType<ChatProps>;
65
86
  export default _default;
@@ -2,8 +2,12 @@ interface ContextProps {
2
2
  className?: string;
3
3
  }
4
4
  /**
5
- * Assembles and shows the exact context blocks a turn would carry, on demand — the one preview of "what does the
6
- * agent see on this screen" that no amount of reading the source answers.
5
+ * Assembles and shows exactly what a turn would carry, on demand — the one preview of "what does the agent see on
6
+ * this screen" that no amount of reading the source answers.
7
+ *
8
+ * The tool list leads, by name only: a zone publishes its tools scope-prefixed, and instructions that name a tool
9
+ * without its prefix name a tool that does not exist. That is invisible in the source of either file and obvious
10
+ * here.
7
11
  */
8
12
  export default function Context({ className }: ContextProps): import("react/jsx-runtime").JSX.Element;
9
13
  export {};
@@ -1,6 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AgentRunner, type CompactOptions } from "../../vendor/use-agentic.d.ts";
2
+ import { type AgentRunner, type AgentSession, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
3
3
  import type { PersistOption } from "./sessionHistory.d.ts";
4
+ import type { BuiltinOption } from "./sessionView.d.ts";
4
5
  export interface ZoneProps {
5
6
  className?: string;
6
7
  /** Names the zone; the scope id and the `data-agent-zone` container both derive from it. */
@@ -12,8 +13,26 @@ export interface ZoneProps {
12
13
  maxTurns?: number;
13
14
  /** When this zone's conversation summarizes itself — same contract as the chat's own `compact`. */
14
15
  compact?: CompactOptions;
15
- /** Keeps this zone's transcript across reloads, keyed by the zone's scope path. */
16
- persist?: PersistOption;
16
+ /**
17
+ * Which of the runtime's own tools this zone's agent gets — all of them by default, `false` none, an array
18
+ * exactly the ones it names. `builtins={["readScreen", "readState"]}` is how a zone that must not leave the
19
+ * screen stops being able to: the tools are withheld, not discouraged, so a prompt cannot talk the model past it.
20
+ */
21
+ builtins?: BuiltinOption;
22
+ /**
23
+ * Keeps this zone's transcript across reloads, keyed by the zone's scope path — web storage by default, or a
24
+ * `SessionHistory` of the app's own to keep it anywhere else, a server included.
25
+ */
26
+ persist?: PersistOption | SessionHistory;
27
+ /** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
28
+ onCompact?: AgentSessionOptions["onCompact"];
29
+ /**
30
+ * Runs this zone on a session the app built instead of one of its own, and the app then owns it: unmounting the
31
+ * zone leaves it running. Read once at mount, like every other session option here.
32
+ */
33
+ session?: AgentSession;
34
+ /** Hands the session out once it exists, for a page or store that wants to send into it or watch it. */
35
+ onSession?: (session: AgentSession) => void;
17
36
  children: ReactNode;
18
37
  }
19
38
  /**
@@ -21,5 +40,9 @@ export interface ZoneProps {
21
40
  * inside — hook tools, `st.use` subscriptions, guides — belongs to this zone's session *and* to the root agent:
22
41
  * zones are views, never walls. An `Agent.Chat` mounted inside binds to this session automatically, so two zones
23
42
  * on one screen run two conversations in parallel, each seeing only its own subtree.
43
+ *
44
+ * **Everything a zone publishes is named `<id>.<name>`.** Instructions that name a tool must carry the prefix —
45
+ * a bare name is a tool that does not exist, and the model calling it spends a turn on `Unknown tool`. Build the
46
+ * name from the id rather than writing it twice, and read `Agent.Context`'s Assemble to see the published list.
24
47
  */
25
- export declare const Zone: ({ className, id, label, instructions, runner, maxTurns, compact, persist, children, }: ZoneProps) => import("react/jsx-runtime").JSX.Element;
48
+ export declare const Zone: ({ className, id, label, instructions, runner, maxTurns, compact, builtins, persist, onCompact, session: provided, onSession, children, }: ZoneProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,6 @@
1
- import { type AgentRunner, AgentSession, type CompactOptions } from "../../vendor/use-agentic.d.ts";
1
+ import { type AgentRunner, AgentSession, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
2
2
  import { type PersistOption } from "./sessionHistory.d.ts";
3
+ import { type BuiltinOption } from "./sessionView.d.ts";
3
4
  export interface AgentSessionSetup {
4
5
  /** Read per call rather than captured, so text the session builds follows a language switched mid-conversation. */
5
6
  l: (key: string) => string;
@@ -9,10 +10,18 @@ export interface AgentSessionSetup {
9
10
  instructions?: string;
10
11
  maxTurns?: number;
11
12
  compact?: CompactOptions;
12
- persist?: PersistOption;
13
+ /**
14
+ * Which of the runtime's own tools this session gets: all of them by default, none with `false`, exactly the
15
+ * ones an array names. A zone whose conversation must stay on one screen takes `navigate` and `goBack` off it.
16
+ */
17
+ builtins?: BuiltinOption;
18
+ /** Web storage by default; a `SessionHistory` puts the transcript wherever the app keeps it, including a server. */
19
+ persist?: PersistOption | SessionHistory;
20
+ /** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
21
+ onCompact?: AgentSessionOptions["onCompact"];
13
22
  }
14
23
  /**
15
24
  * The one place a chat session is wired to the akan runtime. Chat and Zone both build one, and building it twice
16
25
  * is how a zone came to be the only surface with no `compact` option — an option added on one side of a copy.
17
26
  */
18
- export declare const agentSessionOf: ({ l, view, runner, instructions, maxTurns, compact, persist, }: AgentSessionSetup) => AgentSession;
27
+ export declare const agentSessionOf: ({ l, view, runner, instructions, maxTurns, compact, builtins, persist, onCompact, }: AgentSessionSetup) => AgentSession;
@@ -14,5 +14,5 @@ export declare const Agent: {
14
14
  StateKey: typeof StateKey;
15
15
  Tool: typeof Tool;
16
16
  Transcript: typeof Transcript;
17
- Zone: ({ className, id, label, instructions, runner, maxTurns, compact, persist, children, }: import("./Zone.d.ts").ZoneProps) => import("react/jsx-runtime").JSX.Element;
17
+ Zone: ({ className, id, label, instructions, runner, maxTurns, compact, builtins, persist, onCompact, session: provided, onSession, children, }: import("./Zone.d.ts").ZoneProps) => import("react/jsx-runtime").JSX.Element;
18
18
  };
@@ -11,4 +11,4 @@ export type PersistOption = boolean | {
11
11
  * why the cap is applied *before* the pairing repair: the window it keeps can start between a tool call and the
12
12
  * result answering it, and a transcript restored in that state is refused by the provider on its first turn.
13
13
  */
14
- export declare const sessionHistoryOf: (persist: PersistOption | undefined, pathKey?: string) => SessionHistory | undefined;
14
+ export declare const sessionHistoryOf: (persist: PersistOption | SessionHistory | undefined, pathKey?: string) => SessionHistory | undefined;
@@ -0,0 +1,15 @@
1
+ import { StoreSurfaceSource } from "akanjs/store";
2
+ import type { AgenticSurface, SurfaceView } from "../../vendor/use-agentic.d.ts";
3
+ /** One of the tools the akan runtime contributes to every screen, whatever that screen declares. */
4
+ export type AgentBuiltin = (typeof StoreSurfaceSource.builtins)[number];
5
+ /** `true` (the default) takes all of them, `false` none, an array exactly the ones it names. */
6
+ export type BuiltinOption = boolean | AgentBuiltin[];
7
+ /**
8
+ * The half of the surface one session reads: scoped to its zone, and narrowed to the built-ins it was given.
9
+ *
10
+ * Narrowing happens here rather than on the source because the source is shared — two sessions read one registry,
11
+ * and a zone that must not navigate away cannot take `navigate` off the screen for the root agent too. A withheld
12
+ * tool is withheld from `call` as well as from the listing, answering the same "unknown tool" a name that was
13
+ * never registered gets: a tool the model can still reach by guessing its name is not withheld.
14
+ */
15
+ export declare const sessionView: (surface: AgenticSurface, path: string[], builtins?: BuiltinOption) => SurfaceView;
@@ -33,7 +33,7 @@ export declare const Field: {
33
33
  Price: import("react").MemoExoticComponent<({ label, desc, labelClassName, className, value, onChange, placeholder, nullable, disabled, minlength, maxlength, transform, validate, onPressEnter, inputClassName, inputStyleType, }: PriceProps) => import("react/jsx-runtime").JSX.Element>;
34
34
  TextArea: import("react").MemoExoticComponent<({ label, desc, labelClassName, className, value, onChange, placeholder, nullable, disabled, rows, minlength, maxlength, transform, validate, onPressEnter, cache, inputClassName, }: TextAreaProps) => import("react/jsx-runtime").JSX.Element>;
35
35
  Switch: ({ label, desc, labelClassName, className, value, onChange, disabled, inputClassName, onDesc, offDesc, }: SwitchProps) => import("react/jsx-runtime").JSX.Element;
36
- ToggleSelect: <I extends string | number | boolean | null>({ className, labelClassName, label, desc, items, value, validate, onChange, nullable, disabled, btnClassName, }: ToggleSelectProps<I>) => import("react/jsx-runtime").JSX.Element;
36
+ ToggleSelect: <I extends string | number | boolean | null, Nullable extends boolean = false>({ className, labelClassName, label, desc, items, value, validate, onChange, nullable, disabled, btnClassName, }: ToggleSelectProps<I, Nullable>) => import("react/jsx-runtime").JSX.Element;
37
37
  MultiToggleSelect: <I extends string | number | boolean>({ className, labelClassName, label, desc, items, value, minlength, maxlength, validate, onChange, disabled, }: MultiToggleSelectProps<I>) => import("react/jsx-runtime").JSX.Element;
38
38
  TextList: ({ label, desc, labelClassName, className, value, onChange, placeholder, disabled, transform, minlength, maxlength, minTextlength, maxTextlength, cache, validate, inputClassName, }: TextListProps) => import("react/jsx-runtime").JSX.Element;
39
39
  Tags: ({ label, desc, labelClassName, className, value, onChange, placeholder, disabled, transform, minlength, maxlength, minTextlength, maxTextlength, validate, inputClassName, }: TagsProps) => import("react/jsx-runtime").JSX.Element;
@@ -71,7 +71,7 @@ interface ListProps<Item> {
71
71
  label?: string;
72
72
  desc?: string;
73
73
  nullable?: boolean;
74
- value: Item[];
74
+ value: Item[] | null;
75
75
  onChange: (value: Item[]) => void;
76
76
  onAdd: () => void;
77
77
  renderItem: (item: Item, idx: number) => ReactNode;
@@ -137,14 +137,14 @@ interface SwitchProps {
137
137
  desc?: string;
138
138
  labelClassName?: string;
139
139
  className?: string;
140
- value: boolean;
140
+ value: boolean | null;
141
141
  onChange: (value: boolean) => void;
142
142
  inputClassName?: string;
143
143
  onDesc?: string;
144
144
  offDesc?: string;
145
145
  disabled?: boolean;
146
146
  }
147
- interface ToggleSelectProps<I> {
147
+ interface ToggleSelectProps<I, Nullable extends boolean> {
148
148
  className?: string;
149
149
  labelClassName?: string;
150
150
  label?: string;
@@ -156,11 +156,11 @@ interface ToggleSelectProps<I> {
156
156
  value: I;
157
157
  disabled?: boolean;
158
158
  }[] | readonly I[] | I[] | EnumInstance<string, I>;
159
- value: I;
160
- nullable?: boolean;
159
+ value: I | null;
160
+ nullable?: Nullable;
161
161
  disabled?: boolean;
162
162
  validate?: (value: I) => boolean | string;
163
- onChange: (value: I) => void;
163
+ onChange: (value: Nullable extends true ? I | null : I) => void;
164
164
  btnClassName?: string;
165
165
  }
166
166
  interface MultiToggleSelectProps<I extends string | number | boolean> {
@@ -173,7 +173,7 @@ interface MultiToggleSelectProps<I extends string | number | boolean> {
173
173
  value: I;
174
174
  disabled?: boolean;
175
175
  }[] | readonly I[] | I[];
176
- value: I[];
176
+ value: I[] | null;
177
177
  disabled?: boolean;
178
178
  minlength?: number;
179
179
  maxlength?: number;
@@ -185,7 +185,7 @@ interface TextListProps {
185
185
  desc?: string;
186
186
  labelClassName?: string;
187
187
  className?: string;
188
- value: string[];
188
+ value: string[] | null;
189
189
  onChange: (value: string[]) => void;
190
190
  inputClassName?: string;
191
191
  placeholder?: string;
@@ -203,7 +203,7 @@ interface TagsProps {
203
203
  desc?: string;
204
204
  labelClassName?: string;
205
205
  className?: string;
206
- value: string[];
206
+ value: string[] | null;
207
207
  onChange: (value: string[]) => void;
208
208
  inputClassName?: string;
209
209
  placeholder?: string;
@@ -386,7 +386,7 @@ interface ChildrenProps<T extends string, State, Input, Full, Light> {
386
386
  disabled?: boolean;
387
387
  nullable?: boolean;
388
388
  initArgs?: any[];
389
- value: Light[];
389
+ value: Light[] | null;
390
390
  onChange: (value: Light[]) => void;
391
391
  onSearch?: (text: string) => void;
392
392
  slice: SliceMeta;
@@ -402,7 +402,7 @@ interface ChildrenIdProps<T extends string, State, Input, Full, Light> {
402
402
  disabled?: boolean;
403
403
  nullable?: boolean;
404
404
  initArgs?: any[];
405
- value: string[];
405
+ value: string[] | null;
406
406
  slice: SliceMeta;
407
407
  onChange: (value: string[]) => void;
408
408
  onSearch?: (text: string) => void;
@@ -1,4 +1,15 @@
1
1
  import { type Dayjs } from "akanjs/base";
2
+ export type RecentTimeRelativeUnit = "second" | "minute" | "hour" | "day" | "week" | "month" | "year";
3
+ export type RecentTimeRelativeStyle = "fromNow" | "always" | "auto";
4
+ export interface RecentTimeRelative {
5
+ unit: RecentTimeRelativeUnit;
6
+ /** Signed count in `unit`. Negative is past, positive is future, `0` is now. */
7
+ count: number;
8
+ date: Dayjs;
9
+ now: Dayjs;
10
+ defaultLabel: string;
11
+ }
12
+ export type RecentTimeRelativeFormat = RecentTimeRelativeStyle | ((relative: RecentTimeRelative) => string);
2
13
  export interface RecentTimeProps {
3
14
  /** Date value to render. Null renders nothing, and epoch placeholder values render --:--. */
4
15
  date: Date | Dayjs | null;
@@ -6,7 +17,13 @@ export interface RecentTimeProps {
6
17
  breakUnit?: Intl.RelativeTimeFormatUnit;
7
18
  /** Use compact automatic formatting or always include date and time. */
8
19
  format?: "full" | "auto";
20
+ /**
21
+ * Relative phrasing. `"fromNow"` (default) keeps dayjs locale strings (`하루 전`).
22
+ * `"always"` / `"auto"` use `Intl.RelativeTimeFormat` — `1일 전` vs `어제`.
23
+ * A function replaces the relative label; return `defaultLabel` to keep the default.
24
+ */
25
+ relative?: RecentTimeRelativeFormat;
9
26
  /** Additional classes for the trigger span. */
10
27
  className?: string;
11
28
  }
12
- export declare const RecentTime: ({ date, breakUnit, format, className }: RecentTimeProps) => import("react/jsx-runtime").JSX.Element | null;
29
+ export declare const RecentTime: ({ date, breakUnit, format, relative, className }: RecentTimeProps) => import("react/jsx-runtime").JSX.Element | null;