@akanjs/cli 3.0.0-alpha.53 → 3.0.0-alpha.55

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/.build-stamp CHANGED
@@ -1 +1 @@
1
- 75e6ebc02c938b9bfa7b3bbce9bbe0a59753853c08b168184806c0321e3bb71e
1
+ 3dcdd65ef34286ed395d168f590ad6a2698d350ab28abc84526d3e3781eae948
@@ -27,6 +27,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
27
27
  cannot spend the LLM key.
28
28
  `persist` keeps the transcript across reloads (sessionStorage; `{ storage: "local" }` to outlive the tab),
29
29
  default off, and `shortcut={false}` gives the browser back the Cmd/Ctrl+L the launcher otherwise captures.
30
+ **`persist` also takes a `SessionHistory` of the app's own** — `{ load, save, clear }` — which is how a
31
+ transcript lives on a server instead of in web storage. All three may answer asynchronously; a `load` still in
32
+ flight when the user sends the first message is **dropped, not merged**, because the conversation on screen is
33
+ the one they are having — which makes **sending on mount a race the restore loses**, so an opening prompt waits
34
+ for `session.isRestoring` to clear or goes into `defaultDraft` and lets the user send it. Saves are chained rather than fired per change, so a slow store cannot land an older
35
+ transcript last. **Every session option on the chat — `runner`, `instructions`, `maxTurns`, `compact`,
36
+ `builtins`, `persist`, `onCompact` — is ignored when an enclosing `Agent.Zone` or `AgentProvider` already holds
37
+ a session**: the chat binds to that one, and the options belong to whoever built it.
30
38
  **A session the chat made ends when the chat unmounts** — nothing renders its approvals once it is gone, so a
31
39
  turn left running would drive a screen the user has navigated away from; a session handed down by an
32
40
  `AgentProvider` or an `Agent.Zone` belongs to whoever provided it.
@@ -34,7 +42,10 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
34
42
  `open` / `onOpenChange` drive it from the app's own control (`launcher={false}` then draws no launcher of its
35
43
  own), `launcherClassName` / `panelClassName` reach one surface each where `className` reaches both, `intro`
36
44
  replaces the empty-state line — where starter questions go — and `header` adds controls beside the built-in
37
- clear and close. Then the slots: `AgentLauncher`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
45
+ clear and close. `chrome={false}` drops the header bar whole, for an `inline` chat inside a panel the app
46
+ already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
47
+ controlled `open` with **no** `onOpenChange` draws no close button at all rather than an inert one. Then the
48
+ slots: `AgentLauncher`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
38
49
  `AgentQuestion`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
39
50
  `akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
40
51
  one it is replacing. `AgentCode` is where a highlighter binds — the fence's language reaches it — and a
@@ -112,6 +123,24 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
112
123
  `data-agent-zone` container; guides follow the layout cascade (ancestors and own, never a sibling's). Zone
113
124
  membership is positional — there is no per-declaration zone key, so a lib component joins whatever zone the app
114
125
  mounts it in.
126
+ - **`builtins` decides which of the runtime's own tools a session gets** — all of them by default, `false` none,
127
+ an array exactly the ones it names, on `Agent.Zone` and `Agent.Chat` alike. A zone whose conversation must stay
128
+ on one screen takes `navigate` and `goBack` off it: `<Agent.Zone id="wizard" builtins={["readScreen",
129
+ "readState"]}>`. The tools are **withheld, not discouraged** — a withheld name answers the same "unknown tool"
130
+ a name that was never registered gets, so no prompt can talk the model past it, and the narrowing is the
131
+ session's alone: the surface is shared, and the neighbouring zone still navigates. Shadowing a built-in by
132
+ declaring a hook tool of the same name works at the root and **not inside a zone**, where the hook entry is
133
+ registered under its scope-prefixed name and never collides — `builtins` is the zone's answer. A tool the screen
134
+ declares under a built-in name is the screen's, not the runtime's, so `builtins={false}` leaves it standing.
135
+ - **A zone can be handed a session instead of building one**: `<Agent.Zone session={mine} onSession={…}>`. The app
136
+ then owns it, so unmounting the zone leaves it running — the opposite of a session the zone made, which it
137
+ aborts. `onSession` fires either way, for a page that wants to send into the conversation from outside it.
138
+ - **`akanjs/ui` exports what a custom transport or transcript needs**, so no app reaches into the package's
139
+ internals: `httpRunner` (the wire the default runner speaks — point it at an endpoint of the app's own and
140
+ streaming is kept), `fetchRunner` (the default, to wrap), `AgentSession`, `AgentProvider`, and the
141
+ `AgentRunner` / `RunnerRequest` / `RunnerEvent` / `ChatMessage` / `SessionHistory` / `PublishedTool` /
142
+ `ContextBlock` types. `onCompact(replaced, summary)` reports a compaction's cut, for a host that keeps a
143
+ server-side summary and has to move its own watermark to the same place.
115
144
  - **Route guidance is `<Agent.Guide instructions="..." />`** rendered from a `_layout.tsx` or a page — the render
116
145
  tree is the cascade: nested Guides concatenate outer-to-inner and navigating away withdraws them. It is a
117
146
  component, not a pageConfig field. Module `*.abstract.md` files are developer docs and are never served to the
@@ -386,7 +415,8 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
386
415
  ## Built-In Tools
387
416
  - The framework publishes five built-ins on every store surface: `navigate` (internal paths only, the same
388
417
  router `Link` rides), `goBack` (this session's history — global, because history is not a control a page owns and
389
- a page that draws no back link is not one you may not leave), `readScreen` (the rendered DOM as compact text —
418
+ a page that draws no back link is not one you may not leave — a screen that must not be left withholds them
419
+ with `builtins`, not with prose), `readScreen` (the rendered DOM as compact text —
390
420
  headings, links, control values, and `(disabled)` on a control or button that has it; the chat's own UI is
391
421
  skipped via `data-agent-ui`, and a password value is never read), `readState(key)` (one masked store key), and
392
422
  `highlight(target)`. Declaring a hook tool under one of those names shadows the built-in, so reuse them only to
@@ -96,6 +96,17 @@ back.
96
96
  the resolved data as a prop. A component is a PascalCase binding whose own initializer is `async`, so an async
97
97
  handler declared inside a synchronous component, an inline `onClick={async () => …}`, and
98
98
  `lazy(async () => import(…))` are all untouched.
99
+ - **Never call `fetch.init*` from a client file** (`no-init-fetch-in-client.grit`). `fetch.init<Model><Suffix>` and
100
+ `fetch.get<Model>Init<Suffix>` compose the slice's list and insight queries into the hydration snapshot that
101
+ `Load.Units` / `Load.View` seed the store from. From a route it resolves before the first byte; after hydration it
102
+ is two extra round-trips for a shell the browser already painted, landing in a value nothing reads. Load it in the
103
+ page and pass the result down as an `init` prop — or hand the unawaited promise across — and reload from the client
104
+ through the generated `st.do.init<Model><Suffix>()`. The gate is the file: the real `"use client"` directive (the
105
+ same text as a string literal or inside sample code is not one) plus `*.store.ts`, which is client-only by role and
106
+ carries no directive. The name is matched by shape, since a lint rule cannot know which slices exist: the
107
+ generated one always carries two capital-led segments (`init` + `Capitalize<refName>` + `Capitalize<suffix>`), so
108
+ a hand-written `initPayment` is out and `initializeSomething` was never in. `view` / `edit` hydrate alike but are
109
+ left unmatched — `edit<X>` is a plausible custom endpoint name.
99
110
  - `noArrayIndexKey` and `useExhaustiveDependencies` are **off** on purpose: `key={idx}` for embedded scalars and
100
111
  short dependency arrays are intentional, not oversights.
101
112
  - **A grit plugin diagnostic is suppressed as `lint/plugin`, not `plugin`** — `// biome-ignore lint/plugin: <reason>`
@@ -607,10 +618,13 @@ commands, transcript compaction, and the built-in tool semantics: `get_guideline
607
618
  `filter` 0): `title` is the one line a human scans for, `desc` is prose, `tag` is a keyword list, `filter` is a
608
619
  scoping value (status, owner, role) that must be matchable but must never outrank a real title hit.
609
620
  - `thumb` is mirrored for rendering a hit and is **not** indexed — never expect it to match.
610
- - **A `secret`, `hidden`, or `resolve()` field with `text` throws at class-build time**, not at query time. That is
611
- deliberate: the mirror is plaintext, so an indexed secret would leak through search. Do not work around it. The
612
- same throw covers a `text` field *underneath* one of those — a scalar's own field is reachable through its parent,
613
- so `f.secret(Noti)` where `Noti.label` carries a role is rejected at the parent, not silently indexed.
621
+ - **`field.secret`, `field.hidden` and `resolve()` take no `text` role — it is a compile error**, because the
622
+ mirror is plaintext and an indexed secret would leak through search. The class build throws the same refusal as
623
+ a backstop, for an option object the excess-property check cannot see through, and names the way out: drop the
624
+ role, or leave the field unmasked. Do not work around either. The throw also covers a `text` field *underneath*
625
+ one of those — a scalar's own field is reachable through its parent, so `f.secret(Noti)` where `Noti.label`
626
+ carries a role is rejected at the parent, not silently indexed; that one is only reachable at runtime, since the
627
+ pairing spans two files.
614
628
  - The role works on a relation too (`image: field(File, { text: "thumb" })`) and on an array (`playing: field([String],
615
629
  { text: "tag" })`); an array of objects indexes by leaf key, including an array leaf (`works[*].tags`). A field
616
630
  inside a `Map` indexes nothing: there is no fixed path to extract it from.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "3.0.0-alpha.53",
3
+ "version": "3.0.0-alpha.55",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -34,7 +34,7 @@
34
34
  "@langchain/openai": "^1.4.6",
35
35
  "@tailwindcss/node": "^4.3.0",
36
36
  "@trapezedev/project": "^7.1.4",
37
- "akanjs": "3.0.0-alpha.53",
37
+ "akanjs": "3.0.0-alpha.55",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "dayjs": "^1.11.20",