@akanjs/cli 3.0.0-alpha.50 → 3.0.0-alpha.52

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 (46) hide show
  1. package/.build-stamp +1 -1
  2. package/{agent.command-nqs3bw0g.js → agent.command-tsx1nsdj.js} +6 -5
  3. package/{application.command-k2v46pxf.js → application.command-n8ex8g9h.js} +5 -4
  4. package/{applicationBuildRunner-t5v6tcrm.js → applicationBuildRunner-x8h12rp0.js} +2 -1
  5. package/buildBatch.proc.js +3 -2
  6. package/{capacitorApp-927ahhz2.js → capacitorApp-8mv9e537.js} +3 -2
  7. package/{cloud.command-7jwmxtez.js → cloud.command-eqafzn1a.js} +8 -7
  8. package/{context.command-6kn3tady.js → context.command-s7a41qjs.js} +13 -12
  9. package/{guideline.command-pjdrndqk.js → guideline.command-qrw84ttb.js} +3 -2
  10. package/guidelines/agentRule/agentRule.instruction.md +47 -3
  11. package/guidelines/conventions/conventions.instruction.md +22 -1
  12. package/guidelines/mcpRule/mcpRule.instruction.md +18 -6
  13. package/incrementalBuilder.proc.js +2 -1
  14. package/{index-7w5vv8jk.js → index-4mtyaqj2.js} +2 -2
  15. package/{index-90zyjrby.js → index-681sx6nt.js} +6 -1
  16. package/{index-97cw8sg1.js → index-cc1mgv1f.js} +4 -4
  17. package/{index-zgkey4d0.js → index-ce9755av.js} +1 -1
  18. package/{index-8rr621r2.js → index-ckpwq052.js} +1 -1
  19. package/{index-k7sm38v6.js → index-dvg362vq.js} +3 -3
  20. package/{index-866yqz3y.js → index-ek4mkwxp.js} +10 -10
  21. package/{index-6bs24rb4.js → index-f0t3wjyr.js} +2 -2
  22. package/index-f39512t6.js +255 -0
  23. package/{index-5em412h5.js → index-g44fykfw.js} +1 -1
  24. package/{index-xe3ychw5.js → index-h0mnt0b0.js} +3 -3
  25. package/{index-4z05haak.js → index-kkya2ped.js} +1 -1
  26. package/{index-7etegaht.js → index-r8z20vnj.js} +5 -5
  27. package/{index-5fe016p2.js → index-ryns915y.js} +3 -3
  28. package/{index-y59x51f1.js → index-ssrz6xr1.js} +5 -5
  29. package/{index-xrdrttjb.js → index-tn46rr6w.js} +4 -4
  30. package/{index-24h5bqpj.js → index-wc19q8mb.js} +2 -2
  31. package/{index-bwt525ne.js → index-wmwcwn1f.js} +3 -3
  32. package/{index-7zd96bng.js → index-x1dbhd80.js} +2 -2
  33. package/{index-wr999y03.js → index-xj5b8c0w.js} +102 -352
  34. package/index.js +19 -18
  35. package/{library.command-7h3yezwc.js → library.command-p3jgf5h2.js} +4 -3
  36. package/{localRegistry.command-vnkc41gj.js → localRegistry.command-jewr722x.js} +7 -6
  37. package/{module.command-4g11k3xd.js → module.command-hb3xm07r.js} +6 -5
  38. package/{package.command-z7dd6vy2.js → package.command-fbpa6r4w.js} +4 -3
  39. package/package.json +2 -2
  40. package/{page.command-y996708m.js → page.command-92vrmw96.js} +4 -3
  41. package/{primitive.command-4fcn8k83.js → primitive.command-k6b9jfxr.js} +7 -6
  42. package/{quality.command-hx5af2yn.js → quality.command-89vwmjvf.js} +3 -2
  43. package/{repair.command-ysxd8pef.js → repair.command-nh9jn8rh.js} +6 -5
  44. package/{scalar.command-3chgxybs.js → scalar.command-zcsjgvax.js} +5 -4
  45. package/{workflow.command-endxfzv5.js → workflow.command-jj0cphfm.js} +10 -9
  46. package/{workspace.command-5f2nmmd5.js → workspace.command-zg8me6fg.js} +19 -18
package/.build-stamp CHANGED
@@ -1 +1 @@
1
- fc931b5c84401e9bf2bda97ced4e93b6df47eb3293b40b306161d09542b821d7
1
+ 01ea8ed0ffbaa2533de87a90a0f930f8914cabb255e887095d14f13152decabd
@@ -1,15 +1,16 @@
1
1
  // @bun
2
2
  import {
3
3
  AgentScript
4
- } from "./index-xe3ychw5.js";
5
- import"./index-24h5bqpj.js";
6
- import"./index-5em412h5.js";
4
+ } from "./index-h0mnt0b0.js";
5
+ import"./index-wc19q8mb.js";
6
+ import"./index-g44fykfw.js";
7
7
  import"./index-j37qq1f2.js";
8
8
  import {
9
9
  Workspace,
10
10
  command
11
- } from "./index-8rr621r2.js";
12
- import"./index-90zyjrby.js";
11
+ } from "./index-ckpwq052.js";
12
+ import"./index-681sx6nt.js";
13
+ import"./index-f39512t6.js";
13
14
  import"./index-mxvakhsm.js";
14
15
  import"./index-xys926f2.js";
15
16
  import"./index-1577bej2.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "./index-0wae5ebk.js";
5
5
  import {
6
6
  ApplicationScript
7
- } from "./index-7etegaht.js";
8
- import"./index-6bs24rb4.js";
7
+ } from "./index-r8z20vnj.js";
8
+ import"./index-f0t3wjyr.js";
9
9
  import {
10
10
  getMobileTargetChoices
11
11
  } from "./index-76rn3g2c.js";
@@ -15,10 +15,11 @@ import {
15
15
  Sys,
16
16
  Workspace,
17
17
  command
18
- } from "./index-8rr621r2.js";
18
+ } from "./index-ckpwq052.js";
19
19
  import"./index-fgc8r6dj.js";
20
20
  import"./index-5vy2jzar.js";
21
- import"./index-90zyjrby.js";
21
+ import"./index-681sx6nt.js";
22
+ import"./index-f39512t6.js";
22
23
  import"./index-mxvakhsm.js";
23
24
  import"./index-1577bej2.js";
24
25
  import"./index-67546d0j.js";
@@ -4,10 +4,11 @@ import {
4
4
  CsrArtifactBuilder,
5
5
  SsrBaseArtifactBuilder,
6
6
  precompressArtifacts
7
- } from "./index-wr999y03.js";
7
+ } from "./index-xj5b8c0w.js";
8
8
  import"./index-xjd3rqzj.js";
9
9
  import"./index-qnv0aqmm.js";
10
10
  import"./index-5vy2jzar.js";
11
+ import"./index-f39512t6.js";
11
12
  import {
12
13
  Spinner
13
14
  } from "./index-mxvakhsm.js";
@@ -5,14 +5,15 @@ import {
5
5
  FontOptimizer,
6
6
  PagesBundleBuilder,
7
7
  SsrBaseArtifactBuilder
8
- } from "./index-wr999y03.js";
8
+ } from "./index-xj5b8c0w.js";
9
9
  import"./index-xjd3rqzj.js";
10
10
  import"./index-qnv0aqmm.js";
11
11
  import"./index-5vy2jzar.js";
12
12
  import {
13
13
  AppExecutor,
14
14
  WorkspaceExecutor
15
- } from "./index-90zyjrby.js";
15
+ } from "./index-681sx6nt.js";
16
+ import"./index-f39512t6.js";
16
17
  import"./index-mxvakhsm.js";
17
18
  import"./index-1577bej2.js";
18
19
  import"./index-67546d0j.js";
@@ -25,9 +25,10 @@ import {
25
25
  selectLocalDevHost,
26
26
  sortIosRunTargets,
27
27
  writeRootCapacitorConfig
28
- } from "./index-zgkey4d0.js";
28
+ } from "./index-ce9755av.js";
29
29
  import"./index-76rn3g2c.js";
30
- import"./index-90zyjrby.js";
30
+ import"./index-681sx6nt.js";
31
+ import"./index-f39512t6.js";
31
32
  import"./index-mxvakhsm.js";
32
33
  import"./index-1577bej2.js";
33
34
  import"./index-67546d0j.js";
@@ -1,22 +1,23 @@
1
1
  // @bun
2
2
  import {
3
3
  CloudScript
4
- } from "./index-y59x51f1.js";
5
- import"./index-bwt525ne.js";
6
- import"./index-7zd96bng.js";
4
+ } from "./index-ssrz6xr1.js";
5
+ import"./index-wmwcwn1f.js";
6
+ import"./index-x1dbhd80.js";
7
7
  import {
8
8
  GlobalConfig
9
9
  } from "./index-sh01r3pm.js";
10
- import"./index-7etegaht.js";
11
- import"./index-6bs24rb4.js";
10
+ import"./index-r8z20vnj.js";
11
+ import"./index-f0t3wjyr.js";
12
12
  import"./index-76rn3g2c.js";
13
13
  import {
14
14
  Workspace,
15
15
  command
16
- } from "./index-8rr621r2.js";
16
+ } from "./index-ckpwq052.js";
17
17
  import"./index-fgc8r6dj.js";
18
18
  import"./index-5vy2jzar.js";
19
- import"./index-90zyjrby.js";
19
+ import"./index-681sx6nt.js";
20
+ import"./index-f39512t6.js";
20
21
  import"./index-mxvakhsm.js";
21
22
  import"./index-1577bej2.js";
22
23
  import"./index-46tjzh6s.js";
@@ -1,25 +1,26 @@
1
1
  // @bun
2
2
  import {
3
3
  ContextScript
4
- } from "./index-866yqz3y.js";
5
- import"./index-5fe016p2.js";
6
- import"./index-k7sm38v6.js";
7
- import"./index-7w5vv8jk.js";
8
- import"./index-97cw8sg1.js";
9
- import"./index-xrdrttjb.js";
4
+ } from "./index-ek4mkwxp.js";
5
+ import"./index-ryns915y.js";
6
+ import"./index-dvg362vq.js";
7
+ import"./index-4mtyaqj2.js";
8
+ import"./index-cc1mgv1f.js";
9
+ import"./index-tn46rr6w.js";
10
10
  import"./index-ss469dec.js";
11
- import"./index-4z05haak.js";
12
- import"./index-zgkey4d0.js";
11
+ import"./index-kkya2ped.js";
12
+ import"./index-ce9755av.js";
13
13
  import"./index-sh01r3pm.js";
14
- import"./index-24h5bqpj.js";
15
- import"./index-5em412h5.js";
14
+ import"./index-wc19q8mb.js";
15
+ import"./index-g44fykfw.js";
16
16
  import"./index-j37qq1f2.js";
17
17
  import"./index-76rn3g2c.js";
18
18
  import {
19
19
  Workspace,
20
20
  command
21
- } from "./index-8rr621r2.js";
22
- import"./index-90zyjrby.js";
21
+ } from "./index-ckpwq052.js";
22
+ import"./index-681sx6nt.js";
23
+ import"./index-f39512t6.js";
23
24
  import"./index-mxvakhsm.js";
24
25
  import"./index-xys926f2.js";
25
26
  import"./index-1577bej2.js";
@@ -10,8 +10,9 @@ import {
10
10
  command,
11
11
  runner,
12
12
  script
13
- } from "./index-8rr621r2.js";
14
- import"./index-90zyjrby.js";
13
+ } from "./index-ckpwq052.js";
14
+ import"./index-681sx6nt.js";
15
+ import"./index-f39512t6.js";
15
16
  import"./index-mxvakhsm.js";
16
17
  import {
17
18
  getDirname
@@ -29,8 +29,22 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
29
29
  default off, and `shortcut={false}` gives the browser back the Cmd/Ctrl+L the launcher otherwise captures.
30
30
  **A session the chat made ends when the chat unmounts** — nothing renders its approvals once it is gone, so a
31
31
  turn left running would drive a screen the user has navigated away from; a session handed down by an
32
- `AgentProvider` or an `Agent.Zone` belongs to whoever provided it. Re-skin through the `AgentChat` slot in
33
- `_overrides.tsx`.
32
+ `AgentProvider` or an `Agent.Zone` belongs to whoever provided it.
33
+ - **The panel is customized in three widening steps, and most designs stop at the first.** Props first:
34
+ `open` / `onOpenChange` drive it from the app's own control (`launcher={false}` then draws no launcher of its
35
+ own), `launcherClassName` / `panelClassName` reach one surface each where `className` reaches both, `intro`
36
+ 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`,
38
+ `AgentQuestion`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
39
+ `akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
40
+ one it is replacing. `AgentCode` is where a highlighter binds — the fence's language reaches it — and a
41
+ replacement for `AgentBubble` carries its own `memo`, since the transcript re-renders on every delta. Only then
42
+ `AgentChat`, which replaces the panel whole: it also replaces the slash commands, the compaction notice and the
43
+ approval gate, so reach for it when the *layout* differs, not when the look does.
44
+ - **The composer is a textarea**: Enter sends, Shift+Enter writes a newline, and it grows to a few lines before
45
+ it scrolls. The vertical arrows still walk what was sent, but only from the first or last line — anywhere else
46
+ the caret is the textarea's own. On a phone the panel is the whole screen (it is a card from `sm:` up) and it
47
+ lifts above the on-screen keyboard, which only `visualViewport` reports.
34
48
  - **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
35
49
  — or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
36
50
  fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
@@ -269,6 +283,13 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
269
283
  strings a *user* reads: Chat's own buttons go through `l("base.*")`, the model's text never does.
270
284
  - A masked model never crosses the boundary: a value whose `hidden`/`secret` fields are populated is refused at
271
285
  read unless a `mask:` model is named — the same rule and wording as `AgentBridge.read`.
286
+ - **A field the page needs and an agent does not is `field.visual`.** `abstractData: field.visual(String)` — a blur
287
+ placeholder, a rendered HTML body, a serialized geometry: real data the screen renders, and hundreds of tokens per
288
+ record no question is answered from. It is stripped by `mask`, so it leaves every agent-facing read and every MCP
289
+ result, and it is left alone by `resolveReturn`, so the browser still receives it. Unlike `hidden`/`secret` it is
290
+ cost rather than secrecy: nothing is *refused* over one, and it stays an ordinary stored `property`, so
291
+ persistence, search, forms and the page response are untouched. Reach for it whenever a field is bulky and
292
+ useless to a model — that is cheaper than every tool learning to avoid it.
272
293
 
273
294
  ## Slash Commands And The Transcript
274
295
  - **`prompt()` endpoints double as the chat's slash commands.** There is no listing endpoint — the client reads
@@ -315,7 +336,15 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
315
336
  this transcript can shrink. One that *landed* and shrank nothing does, so that one is not retried until another
316
337
  threshold's worth has been added. On the
317
338
  wire a summary wears the user's role because the wire has no other, so a provider mapping frames it as a system
318
- message and `/retry` steps over it — replaying it would send the notes back as a question.
339
+ message and `/retry` steps over it — replaying it would send the notes back as a question. **While a summary is
340
+ being written the transcript says so** (`session.isCompacting`, distinct from `isRunning`, which an
341
+ auto-compaction runs inside): that turn takes as long as any other and answers nothing the user asked, so a chat
342
+ that only shows the running dot reads as a question being ignored.
343
+ - **The transcript shows what each turn costs, because nothing else can.** The header carries the whole
344
+ conversation's estimated tokens (`session.tokens`), and every settled tool row carries its own and **opens onto
345
+ the value the model was handed** — a tool result is the one part of a transcript neither the user nor the app
346
+ author ever sees, being the app's own return value. A window that filled after four messages is explained by
347
+ *which* row cost a million tokens and by nothing else, so that is the question the row answers in one click.
319
348
  - **A stopped turn answers the calls it never ran, because an unanswered call ends the conversation.** Every
320
349
  provider dialect refuses an assistant message whose `tool_calls` have no results — on that turn and on every
321
350
  later one — so Stop landing between a call and its result would leave a transcript nothing can be sent from,
@@ -385,6 +414,21 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
385
414
  `highlight` scrolls its target into view and flashes it **once the scroll lands**, since a smooth scroll across a
386
415
  long page outlasts the flash; it is the one built-in that exists for the *user's* benefit, because showing where
387
416
  a control is beats writing directions to it.
417
+ - **`<Agent.Skip label="site footer">` leaves a region out of the default read** — chrome that costs tokens and
418
+ answers nothing: a footer, a cookie banner, a nav that repeats on every route. What stands in its place is
419
+ `[skipped: site footer]`, never nothing, because a deleted region reads as an absent one and an agent asked about
420
+ the footer would answer that the page has none. The marker's name **is** a `section`, so naming it reads the
421
+ region after all: what the marker withholds is the *default* read, not the region. It renders a wrapper div, so
422
+ where that would move a flex or grid layout put `data-agent-skip="<name>"` on the element the page already
423
+ renders — an unlabelled attribute falls back to the tag name, which is why the raw form belongs on a `<footer>`
424
+ or `<nav>` and the component insists on a label. A region that is hidden anyway leaves no marker claiming it is
425
+ there. **It hides text, not behaviour**: tools and state keys are declarations rather than markup, so an
426
+ `st.tool` inside is published exactly as before and `highlight` still reaches a control in there. This is
427
+ `field.visual` one layer up — cost, not secrecy.
428
+ - **Reach for it second.** A read narrows from the other side too: `Agent.Zone` and `readScreen({ section })` scope
429
+ to one container, which beats blocklisting five regions on a screen that is mostly chrome. And a footer is last
430
+ in the document, so on a page long enough to truncate it was already past the cut — the regions worth marking are
431
+ the ones *above* the content.
388
432
  - **Every tool's return value is bounded at 20,000 characters before it enters the transcript** (`ToolOutput.limit`,
389
433
  clipped with a note that gives the real size and tells the model to read a narrower part). `readScreen` caps
390
434
  itself; nothing else did, and a store key is sized for a screen rather than for a model's window — one
@@ -89,6 +89,13 @@ back.
89
89
  implements the boundary and is where the two graphs legitimately meet.
90
90
  - **Server-component discipline** is enforced on `page/**`, `*.Unit.tsx`, and `*.View.tsx`
91
91
  (`no-import-client-functions.grit`, `no-use-client-in-server.grit`, `non-scalar-props-restricted.grit`).
92
+ - **Never write an async component outside `page/`** (`no-async-component-in-ui.grit`, scoped to
93
+ `{apps,libs}/**/ui/**/*.tsx`). React has no async client component, so a `ui/` component that awaits breaks as
94
+ soon as a client parent renders it, and the load drops below the route, which could have started it before the
95
+ first byte. Await in the page — or hand an unawaited `fetch.*` to a `Zone` as an `init` / `view` prop — and take
96
+ the resolved data as a prop. A component is a PascalCase binding whose own initializer is `async`, so an async
97
+ handler declared inside a synchronous component, an inline `onClick={async () => …}`, and
98
+ `lazy(async () => import(…))` are all untouched.
92
99
  - `noArrayIndexKey` and `useExhaustiveDependencies` are **off** on purpose: `key={idx}` for embedded scalars and
93
100
  short dependency arrays are intentional, not oversights.
94
101
  - **A grit plugin diagnostic is suppressed as `lint/plugin`, not `plugin`** — `// biome-ignore lint/plugin: <reason>`
@@ -490,6 +497,12 @@ while guaranteeing that every endpoint added later is invisible to agents until
490
497
  - A refused endpoint answers the *same* "unknown tool" as one that does not exist, and a guard's refusal is
491
498
  generalized to `You are not permitted to perform this action.` Never make either message more helpful — the
492
499
  difference is what enumerates your private surface.
500
+ - **A `field.visual` field is stripped from every MCP result**, and from the readable schema so the two agree — the
501
+ model-level answer to a field the page needs and an agent does not. `resolveReturn` leaves it alone, so the
502
+ browser still receives it.
503
+ - **A structured result ships twice by default** — once as `structuredContent`, once as the same JSON in the text
504
+ block, which is what the spec asks for clients predating the structured field and is also a flat doubling of what
505
+ every model-returning tool costs. `option.setMcp({ legacyTextBlock: false })` leaves a pointer there instead.
493
506
  - `AKAN_MCP_READONLY=true` is the read-only-deployment valve, not the exposure switch; `AKAN_MCP=false` takes the
494
507
  whole surface off.
495
508
  - **`prompt()`** is invoked by the *user* as a slash command, never chosen by the model. `exec` returns
@@ -536,7 +549,14 @@ package; apps and libs never import it directly (`no-import-external-library`)
536
549
  for a screen and not for a model's window: one `readState` of a list whose rows carry inlined bytes is megabytes,
537
550
  and from there it rides *every* later turn. Each tool row in the chat carries its own estimated token cost and
538
551
  opens onto the value the model was handed — that row is where a conversation that filled up in four messages
539
- explains itself.
552
+ explains itself. A field that is bulky and useless to a model is fixed once at the model instead:
553
+ `field.visual(String)` keeps it on the page and out of every agent-facing read and MCP result.
554
+ - **`<Agent.Skip label="site footer">` leaves a region out of the default `readScreen`** — a footer, a cookie
555
+ banner, a nav that repeats on every route. `[skipped: site footer]` stands in its place rather than nothing, since
556
+ a deleted region reads as an absent one, and the marker's name is a `section` that reads the region on request.
557
+ Put `data-agent-skip="<name>"` on the element the page already renders where a wrapper div would move the layout.
558
+ It hides **text, not behaviour**: an `st.tool` inside is published as before. Narrowing from the other side —
559
+ `Agent.Zone`, `readScreen({ section })` — is the better move on a screen that is mostly chrome.
540
560
  - **Route guidance is `<Agent.Guide instructions="..." />`** rendered from a `_layout.tsx` or page — the render
541
561
  tree is the cascade. It is a component, not a `pageConfig` field, and `*.abstract.md` is never served to agents.
542
562
  - The framework publishes five built-ins on every store surface — `navigate`, `goBack`, `readScreen`,
@@ -558,6 +578,7 @@ commands, transcript compaction, and the built-in tool semantics: `get_guideline
558
578
  - Follow the established model layering pattern in this order: `Input`, `Object`, `Light<Model>`, full `<Model>`, and `<Model>Insight`. Write all five, and write `<Model>Insight` even when it is empty.
559
579
  - Put display and predicate logic on the `Light<Model>` class rather than in a util module — see Module File Playbook.
560
580
  - Defaults are a literal for scalars and a thunk for anything constructed. Arrays are `field([T])`; optional is the postfix `.optional()`.
581
+ - **`field.visual(T)` is a field the page renders and an agent never sees** — a blur placeholder, a rendered HTML body, a serialized geometry. It stays an ordinary stored `property` (persistence, search, forms and the page response untouched) and is stripped wherever a value is masked for an AI caller: every in-page-agent read and every MCP result, along with the MCP readable schema. Unlike `hidden`/`secret` it is cost, not secrecy — nothing is refused over one. Reach for it whenever a field is bulky and useless to a model; that is cheaper than every tool learning to avoid it.
561
582
 
562
583
  ### Scalar & Field Type Reference
563
584
 
@@ -16,13 +16,13 @@ until somebody remembers it.
16
16
 
17
17
  ## Configuration
18
18
  Settings live in the app's `lib/option.ts` — `option.setMcp({ … })`, taking `enabled`, `readOnly`, `path`,
19
- `version`, `instructions`, `allowedOrigins`, `pageSize`, `language`, and `auth`. **Not `main.ts`**: the gateway
19
+ `version`, `instructions`, `allowedOrigins`, `pageSize`, `language`, `legacyTextBlock`, and `auth`. **Not `main.ts`**: the gateway
20
20
  there only spawns children, and `option.ts` is the app-authored file `server.ts` already hands to the process that
21
21
  mounts `/mcp`. Every lib's option is read in mount order with the app's last, so an app tightens what a library
22
22
  declared without restating it. Each field also has an env spelling (`AKAN_MCP`, `AKAN_MCP_READONLY`,
23
23
  `AKAN_MCP_PATH`, `AKAN_MCP_VERSION`, `AKAN_MCP_INSTRUCTIONS`, `AKAN_MCP_ALLOWED_ORIGINS`, `AKAN_MCP_PAGE_SIZE`,
24
- `AKAN_MCP_LANGUAGE`, `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, `AKAN_MCP_RESOURCE`) for a deployment that
25
- configures what the source does not, which the option overrides.
24
+ `AKAN_MCP_LANGUAGE`, `AKAN_MCP_LEGACY_TEXT`, `AKAN_MCP_AUTH_SERVERS`, `AKAN_MCP_SCOPES`, `AKAN_MCP_RESOURCE`) for a
25
+ deployment that configures what the source does not, which the option overrides.
26
26
  The two booleans answer to `AKAN_PUBLIC_MCP` / `AKAN_PUBLIC_MCP_READONLY` too, the same pairing `AKAN_OPENAPI`
27
27
  has, and a value written in code wins over the env of the same name — an explicit `undefined` is not a value.
28
28
  `AKAN_MCP_PATH` is normalized to a leading `/`, because the route key and the OAuth metadata path are both built by
@@ -87,9 +87,21 @@ export class TaskEndpoint extends endpoint(srv.task, ({ mutation, prompt }) => (
87
87
  can — a list is wrapped as `{ items: … }` for the same reason — and a declared schema obliges every result to
88
88
  match it, so a client SDK throws on the first call that finds nothing. A nullable *list* keeps its schema, and a
89
89
  scalar return has no structured half at all: it ships as the value itself, not as JSON.
90
- - **An `outputSchema` names no `hidden` or `secret` field.** Every response has both stripped, so publishing them
91
- promises a property no answer can carry — and on a model like `user` the names are the leak. Your *input* schema
92
- keeps them: they are legal to send, and the same model describes a request body.
90
+ - **An `outputSchema` names no `hidden`, `secret`, or `visual` field.** Every response has the first two stripped,
91
+ so publishing them promises a property no answer can carry — and on a model like `user` the names are the leak.
92
+ Your *input* schema keeps all three: they are legal to send, and the same model describes a request body.
93
+ - **A `field.visual` is stripped from every MCP result.** It is the marker for a field the page renders and no
94
+ question is answered from — a blur placeholder, a rendered HTML body — and it is cost rather than secrecy, so
95
+ nothing is refused over one. The strip happens in the MCP dispatcher rather than in `resolveReturn`, which every
96
+ ordinary HTTP response also passes through: the whole point is that a browser still receives it. That is also why
97
+ the readable schema drops it — a non-optional visual field would otherwise be listed `required` and a validating
98
+ client would refuse a result that correctly omits it.
99
+ - **A structured result ships twice by default**, once as `structuredContent` and once as the same JSON in the text
100
+ block, which is what the spec asks of a server for clients predating the structured field. It is also a flat
101
+ doubling of what every model-returning tool costs a model. `option.setMcp({ legacyTextBlock: false })` — or
102
+ `AKAN_MCP_LEGACY_TEXT=false` — leaves a one-line pointer in the text block instead, for a deployment whose
103
+ clients read the structured half. A scalar return is unaffected: it has no structured half to point at, so it
104
+ ships as the value itself either way.
93
105
  - A refused endpoint answers the *same* "unknown tool" as one that does not exist. Never make that
94
106
  message more helpful — the difference is what enumerates your private surface. A guard's refusal is generalized
95
107
  the same way: the caller reads `You are not permitted to perform this action.`, never `Access denied by guard:
@@ -16,7 +16,8 @@ import {
16
16
  import {
17
17
  AppExecutor,
18
18
  WorkspaceExecutor
19
- } from "./index-90zyjrby.js";
19
+ } from "./index-681sx6nt.js";
20
+ import"./index-f39512t6.js";
20
21
  import"./index-mxvakhsm.js";
21
22
  import"./index-1577bej2.js";
22
23
  import"./index-67546d0j.js";
@@ -9,14 +9,14 @@ import {
9
9
  createPassedPrimitiveReport,
10
10
  generatedFilesForSync,
11
11
  scalarChangedFiles
12
- } from "./index-5em412h5.js";
12
+ } from "./index-g44fykfw.js";
13
13
  import {
14
14
  Prompter
15
15
  } from "./index-j37qq1f2.js";
16
16
  import {
17
17
  runner,
18
18
  script
19
- } from "./index-8rr621r2.js";
19
+ } from "./index-ckpwq052.js";
20
20
 
21
21
  // pkgs/@akanjs/cli/scalar/scalar.prompt.ts
22
22
  import { input } from "@inquirer/prompts";
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ getRootBoundarySegments,
4
+ isRootBoundarySegments
5
+ } from "./index-f39512t6.js";
2
6
  import {
3
7
  Spinner
4
8
  } from "./index-mxvakhsm.js";
@@ -2746,7 +2750,8 @@ class AppExecutor extends SysExecutor {
2746
2750
  }
2747
2751
  if (!owner)
2748
2752
  owners.set(routeId, { absPath, fromLib });
2749
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
2753
+ const layoutSegments = getRootBoundarySegments(key);
2754
+ const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig2.basePaths);
2750
2755
  const routeSource = await Bun.file(absPath).text();
2751
2756
  const validator = await AppExecutor.#getRouteSourceValidator();
2752
2757
  if (parsed.kind === "overrides")
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  ModuleScript
4
- } from "./index-xrdrttjb.js";
4
+ } from "./index-tn46rr6w.js";
5
5
  import {
6
6
  addFieldUiPolicyForType,
7
7
  coerceFieldDefault,
@@ -34,15 +34,15 @@ import {
34
34
  sourceFile,
35
35
  validationCommandsForTarget,
36
36
  viaBuilderParameterName
37
- } from "./index-5em412h5.js";
37
+ } from "./index-g44fykfw.js";
38
38
  import {
39
39
  script
40
- } from "./index-8rr621r2.js";
40
+ } from "./index-ckpwq052.js";
41
41
  import {
42
42
  AppExecutor,
43
43
  LibExecutor,
44
44
  ModuleExecutor
45
- } from "./index-90zyjrby.js";
45
+ } from "./index-681sx6nt.js";
46
46
 
47
47
  // pkgs/@akanjs/cli/primitive/primitive.script.ts
48
48
  import { capitalize } from "akanjs/common";
@@ -5,7 +5,7 @@ import {
5
5
  } from "./index-76rn3g2c.js";
6
6
  import {
7
7
  CommandExecutionError
8
- } from "./index-90zyjrby.js";
8
+ } from "./index-681sx6nt.js";
9
9
 
10
10
  // pkgs/@akanjs/devkit/capacitorApp.ts
11
11
  import { cp, mkdir, readFile, rm, writeFile } from "fs/promises";
@@ -6,7 +6,7 @@ import {
6
6
  ModuleExecutor,
7
7
  PkgExecutor,
8
8
  WorkspaceExecutor
9
- } from "./index-90zyjrby.js";
9
+ } from "./index-681sx6nt.js";
10
10
  import {
11
11
  getDirname
12
12
  } from "./index-1577bej2.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AkanContextAnalyzer
4
- } from "./index-24h5bqpj.js";
4
+ } from "./index-wc19q8mb.js";
5
5
  import {
6
6
  WorkflowExecutor,
7
7
  compactWorkflowInputs,
@@ -23,10 +23,10 @@ import {
23
23
  workflowCommandsForPlan,
24
24
  workflowPlanApproval,
25
25
  writeWorkflowRunArtifact
26
- } from "./index-5em412h5.js";
26
+ } from "./index-g44fykfw.js";
27
27
  import {
28
28
  runner
29
- } from "./index-8rr621r2.js";
29
+ } from "./index-ckpwq052.js";
30
30
 
31
31
  // pkgs/@akanjs/cli/workflow/workflow.runner.ts
32
32
  import { mkdir, readFile, writeFile } from "fs/promises";
@@ -1,22 +1,22 @@
1
1
  // @bun
2
2
  import {
3
3
  RepairRunner
4
- } from "./index-5fe016p2.js";
4
+ } from "./index-ryns915y.js";
5
5
  import {
6
6
  WorkflowRunner
7
- } from "./index-k7sm38v6.js";
7
+ } from "./index-dvg362vq.js";
8
8
  import {
9
9
  ScalarScript
10
- } from "./index-7w5vv8jk.js";
10
+ } from "./index-4mtyaqj2.js";
11
11
  import {
12
12
  PrimitiveScript
13
- } from "./index-97cw8sg1.js";
13
+ } from "./index-cc1mgv1f.js";
14
14
  import {
15
15
  ModuleScript
16
- } from "./index-xrdrttjb.js";
16
+ } from "./index-tn46rr6w.js";
17
17
  import {
18
18
  isPlaceholderAppId
19
- } from "./index-zgkey4d0.js";
19
+ } from "./index-ce9755av.js";
20
20
  import {
21
21
  AkanContextAnalyzer,
22
22
  akanMcpInstallConfigPaths,
@@ -27,14 +27,14 @@ import {
27
27
  renderDoctorText,
28
28
  resourceList,
29
29
  upsertCodexMcpServerBlock
30
- } from "./index-24h5bqpj.js";
30
+ } from "./index-wc19q8mb.js";
31
31
  import {
32
32
  buildAkanModuleContextIndex,
33
33
  createWorkflowBaselineSummary,
34
34
  createWorkflowStepRegistry,
35
35
  jsonText,
36
36
  toolingRolloutGate
37
- } from "./index-5em412h5.js";
37
+ } from "./index-g44fykfw.js";
38
38
  import {
39
39
  Prompter
40
40
  } from "./index-j37qq1f2.js";
@@ -45,10 +45,10 @@ import {
45
45
  CommandContainer,
46
46
  runner,
47
47
  script
48
- } from "./index-8rr621r2.js";
48
+ } from "./index-ckpwq052.js";
49
49
  import {
50
50
  AppExecutor
51
- } from "./index-90zyjrby.js";
51
+ } from "./index-681sx6nt.js";
52
52
 
53
53
  // pkgs/@akanjs/cli/context/context.script.ts
54
54
  import { Logger } from "akanjs/common";
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-8rr621r2.js";
5
+ } from "./index-ckpwq052.js";
6
6
  import {
7
7
  LibExecutor
8
- } from "./index-90zyjrby.js";
8
+ } from "./index-681sx6nt.js";
9
9
 
10
10
  // pkgs/@akanjs/cli/semver.ts
11
11
  var stripRangeOperator = (version) => version.replace(/^[\s^~>=<v]+/, "").trim();