@akanjs/cli 3.0.0-alpha.29 → 3.0.0-alpha.30
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
|
-
|
|
1
|
+
6666c78ff8b3c6d6f59ad62c78bd4c8771fbd5e4f66acfa5bb2402f05b6ed982
|
|
@@ -679,6 +679,44 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
679
679
|
unconditional statement, and the callable carries `data-akan-action` like a store setter does. A `remove*` name
|
|
680
680
|
defaults to a confirm gate. Reach a store action from the body — `.exec((id) => st.do.removeX(id))` — which is
|
|
681
681
|
how an agent gets CRUD; `st.do` on its own reaches nobody.
|
|
682
|
+
- **A falsy name declares the tool without publishing it** — the callable still drives the click a person makes,
|
|
683
|
+
and the agent never learns the tool exists. That is the only way a conditional surface stays legal, because
|
|
684
|
+
`.exec()` is a hook and the declaration can never be skipped: withhold the name, not the call. `st.useState`
|
|
685
|
+
and `st.expose` take a falsy name the same way, and an unpublished callable carries no `data-akan-action` —
|
|
686
|
+
that attribute names a tool an agent can reach. **Publish a tool only where the screen already renders the
|
|
687
|
+
control**: a lever no one can pull by hand is not one to hand an agent, and every published tool is paid for in
|
|
688
|
+
every turn's prompt. The mirror of the same rule is why the control gets a tool at all.
|
|
689
|
+
- **An `enumOf` class is a complete argument type on its own**: `.arg("mode", TaskStatus)` publishes the values as
|
|
690
|
+
the argument's `enum`, refuses anything off them by name at call time, and narrows the `.exec` parameter to the
|
|
691
|
+
value union — nothing else to write, and the scalar (`string` / `integer` / `number`) comes from the values.
|
|
692
|
+
**A value set the *render* decides takes `.arg(name, type, { oneOf })`** instead, because `enumOf` registers
|
|
693
|
+
globally and a component cannot build one per render: pass the list it has — a slice's sort keys, the options a
|
|
694
|
+
prop carried — and it is published and enforced the same way. Neither reaches a set that fills in *after* the
|
|
695
|
+
first render, since a declaration is mount-static; put that in the tool's `guard`, which is re-read per call and
|
|
696
|
+
can name the current values in its refusal.
|
|
697
|
+
- **A component that renders once per row publishes nothing.** A tool registered under one name by fifty rows is
|
|
698
|
+
forty-nine collisions and one survivor. The container publishes one tool taking the id instead —
|
|
699
|
+
`removeTask(taskId)`, never fifty `removeTask` — and the agent reads the ids from the `<slice>.items` resource
|
|
700
|
+
`Load.Units` and `Data.ListContainer` already expose.
|
|
701
|
+
- **`akanjs/ui` publishes its own controls, so an app writes nothing for them.** `Data.ListContainer` (and every
|
|
702
|
+
`Model.AdminPanel`) publishes its toolbar and its row and modal verbs; `Load.Units`, `Load.Pagination` and
|
|
703
|
+
`Data.Pagination` publish `setPageOf<Model>`; `Layout.Sider`, `System.SelectLanguage`, `Link.Back` and
|
|
704
|
+
`System.ThemeToggle` publish the shell. **A component that can render twice on one screen takes a
|
|
705
|
+
`namespace` prop and publishes nothing without it** — `Tab`, `Dialog`, `ScreenNavigator`. Pass one
|
|
706
|
+
(`<Tab namespace="detail">`) and the tool becomes `switchTabInDetail`; leave it off and that tab is invisible to
|
|
707
|
+
the agent, because two tabs answering to `switchTab` would mean the first to mount loses.
|
|
708
|
+
- **A form control publishes its own setter, and reading a form publishes one tool that fills several at once.**
|
|
709
|
+
Both are free: an app writes no `st.tool` for a form. A `Field.*` / `Input.*` / `Select` / `Switch` handed
|
|
710
|
+
`onChange={st.do.setTitleOnTask}` **by reference** publishes `setTitleOnTask` while it is on screen — the same
|
|
711
|
+
reference that earns `data-akan-action`, so the tool and the person press one function and an inline arrow
|
|
712
|
+
still publishes nothing. `st.use.taskForm()` adds `fillTaskForm(patch)`, which takes several fields in one call
|
|
713
|
+
and is the only way to reach a list, a map, or an embedded object, whose rows are written through
|
|
714
|
+
`writeOnTask(path, value)` and can carry no annotation. It is a patch: a field left out keeps its value.
|
|
715
|
+
`fillTaskForm` refuses a plain field whose control is not on screen and names the ones that are; a composite it
|
|
716
|
+
cannot see is let through, which is the one place an agent reaches a field the screen may not draw. Never a
|
|
717
|
+
relation (picked or uploaded, not typed), a base document field, or a `hidden`/`secret` one at any depth —
|
|
718
|
+
their reads are masked and a writer would be the door around that. `st.use.taskForm({ agent: false })`
|
|
719
|
+
withholds the patch tool; an inline arrow withholds a control's own.
|
|
682
720
|
- **Reading is per key, not per store.** `st.useState(name, initial, meta)` publishes local state (read-only
|
|
683
721
|
unless `set:` names a type) and `st.expose(name, value)` a derived value. A subscribed store key is listed in
|
|
684
722
|
the state context block by name and pulled with `readState(key)`, masked by the model that key declares — while
|
|
@@ -694,11 +732,50 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
694
732
|
- **`prompt()` endpoints double as the chat's slash commands.** There is no listing endpoint — the client reads
|
|
695
733
|
its own serialized signals — so a prompt's dictionary `.desc()` is what the menu shows, and its guards are
|
|
696
734
|
enforced by the prompt's own GET at call time.
|
|
697
|
-
- The framework publishes
|
|
698
|
-
router `Link` rides), `
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
735
|
+
- The framework publishes five built-ins on every store surface: `navigate` (internal paths only, the same
|
|
736
|
+
router `Link` rides), `goBack` (this session's history — global, because history is not a control a page owns and
|
|
737
|
+
a page that draws no back link is not one you may not leave), `readScreen` (the rendered DOM as compact text —
|
|
738
|
+
headings, links, control values; the chat's own UI is skipped via `data-agent-ui`, and a password value is never
|
|
739
|
+
read), `readState(key)` (one masked store key), and `highlight(target)`. Declaring a hook tool under one of those
|
|
740
|
+
names shadows the built-in, so reuse them only to mean that.
|
|
741
|
+
- **A tool that changes the screen waits for the screen before it answers.** `router.push` returns while the RSC
|
|
742
|
+
payload is still in flight and a store action that fires `void fetch.*` commits a tick later, so `navigate`
|
|
743
|
+
awaits `ScreenSettle.wait()` — DOM quiescence, bounded, because the client router hands its promise to nobody —
|
|
744
|
+
and the session awaits it after every non-`query` tool before taking the change report. Without it the report
|
|
745
|
+
describes the moment before the change landed and the `readScreen` that follows reads the page the user left.
|
|
746
|
+
New tools and state from a fresh route are still only listed from the next turn: the catalogue is snapshotted
|
|
747
|
+
when the turn starts.
|
|
748
|
+
- **`readScreen` takes a `section`, and `highlight` a `target`.** Both resolve a name the agent has already seen —
|
|
749
|
+
a `data-akan-action` / `data-akan-state` annotation, an `Agent.Zone` or `useScreenScope` container
|
|
750
|
+
(`data-agent-scope`, which `Load.Units` / `Load.View` / `Data.ListContainer` put on the container they render),
|
|
751
|
+
an element id, or **a heading by its own text**, matched on letters and digits so the slug an agent writes for a
|
|
752
|
+
heading it read resolves. That tolerance stops at headings: a heading is a landmark and scrolling to the wrong
|
|
753
|
+
one costs nothing, while two buttons reading "Save" are not the same control. **Nothing hidden ever resolves** —
|
|
754
|
+
a ring nobody can see reads as a broken tool, not as a miss. A section named by a heading is read to the next
|
|
755
|
+
heading of its level or higher.
|
|
756
|
+
- **A screen is only aimable if its names are printed.** `readScreen` writes `(#anchor)` beside a heading that
|
|
757
|
+
opens an id'd or scoped container, and a truncated read ends with the headings below the cut — otherwise
|
|
758
|
+
everything past the 8000-character limit is unreachable, because nothing names it, and an agent asked to point
|
|
759
|
+
at a section it cannot name guesses a slug and is refused. A refusal lists the sections actually on screen.
|
|
760
|
+
`highlight` scrolls its target into view and flashes it **once the scroll lands**, since a smooth scroll across a
|
|
761
|
+
long page outlasts the flash; it is the one built-in that exists for the *user's* benefit, because showing where
|
|
762
|
+
a control is beats writing directions to it.
|
|
763
|
+
- **A slow tool reports its own progress with `AgentProgress.report(message, { done, total })`** from wherever the
|
|
764
|
+
work is — a store action, an upload loop, an adapter — reached through a module slot rather than a parameter, and
|
|
765
|
+
a no-op when nobody is rendering it. The chat shows it on that call's row until the row resolves. It is the
|
|
766
|
+
browser twin of `McpProgress.report`.
|
|
767
|
+
- **The turn cap is a question, not a dead end.** At `maxTurns` the session asks whether to keep going through the
|
|
768
|
+
same card `askUser` uses, and the answer rides as the user's own turn — so a steer typed instead of the
|
|
769
|
+
keep-going choice reaches the model as guidance. A host that renders no `pendingQuestion` passes no
|
|
770
|
+
`continueAsk` and keeps the old failure, because asking with nobody listening would hang.
|
|
771
|
+
- **`askUser` is a fourth built-in the *session* owns, not the surface.** The answer comes from the conversation
|
|
772
|
+
rather than the screen, so it rides on every turn whatever the page declares, and a zone agent asks inside its
|
|
773
|
+
own transcript. `choices` offers a pick (`multiple` for several) and omitting them asks for free text; the card
|
|
774
|
+
keeps a free-text row either way, because the model wrote the options and only the user knows whether the answer
|
|
775
|
+
is among them. The loop parks on the question exactly as it parks on an approval, a dismissal is the tool's
|
|
776
|
+
error result rather than a silent empty answer, and the settled exchange renders as question-and-answer instead
|
|
777
|
+
of a tool row. **Never re-implement it per screen** — a `st.tool("askAboutX")` that opens a modal is the same
|
|
778
|
+
thing with a worse transcript — and a hook tool named `askUser` shadows it like any other built-in.
|
|
702
779
|
|
|
703
780
|
## Scalar Modeling (`**/*.constant.ts`)
|
|
704
781
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.30",
|
|
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.
|
|
37
|
+
"akanjs": "3.0.0-alpha.30",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"dayjs": "^1.11.20",
|