@akanjs/cli 3.0.0-alpha.41 → 3.0.0-alpha.43
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
|
+
d8a476b01467d4b59495cc5a6332930a9ce6ba7001dd3a4bf3510bf13d6cd839
|
|
@@ -680,7 +680,11 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
680
680
|
guard classes every endpoint takes (an array is ANDed, `null` clears what a library set); without one the chat
|
|
681
681
|
cannot spend the LLM key.
|
|
682
682
|
`persist` keeps the transcript across reloads (sessionStorage; `{ storage: "local" }` to outlive the tab),
|
|
683
|
-
default off
|
|
683
|
+
default off, and `shortcut={false}` gives the browser back the Cmd/Ctrl+L the launcher otherwise captures.
|
|
684
|
+
**A session the chat made ends when the chat unmounts** — nothing renders its approvals once it is gone, so a
|
|
685
|
+
turn left running would drive a screen the user has navigated away from; a session handed down by an
|
|
686
|
+
`AgentProvider` or an `Agent.Zone` belongs to whoever provided it. Re-skin through the `AgentChat` slot in
|
|
687
|
+
`_overrides.tsx`.
|
|
684
688
|
- **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
|
|
685
689
|
— or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
|
|
686
690
|
fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
|
|
@@ -689,6 +693,13 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
689
693
|
providers the way middleware is applied: `option.applyAdaptor(LlmAdaptorRole, ClaudeLlm)`, where the
|
|
690
694
|
implementation is an `adapt()` class in a `srvkit/` implementing `LlmAdaptor.chat(request, onDelta?)` — ignore
|
|
691
695
|
`onDelta` and the chat still answers whole.
|
|
696
|
+
- **An adaptor answers `null` for "not configured" and *throws* for a refusal it can explain.** The two are
|
|
697
|
+
different things to be told: collapsing both into `null`, the way the adapter convention otherwise reads, left a
|
|
698
|
+
user reading `llmUnavailable` — "no model is configured" — about a conversation that had merely outgrown the
|
|
699
|
+
context window. A thrown `Err` reaches the chat as its own text, so the reason the provider gave is what the
|
|
700
|
+
user sees. It travels as the dictionary key plus the values that key interpolates, on both the JSON and the SSE
|
|
701
|
+
path, and **`fetchRunner` resolves it against the dictionary one step before the transcript** — the endpoint has
|
|
702
|
+
no language to resolve it in and the browser does, which is why the key was reaching the screen raw.
|
|
692
703
|
- **A file the user attaches rides the message, and nothing is stored.** The composer takes a paperclip, a drop and
|
|
693
704
|
a paste; an image rides as bytes and a text file as text, which is all a browser reads with no dependency.
|
|
694
705
|
Everything else is the app's own reader — `<Agent.Chat attach={…} />`, one `File` in, a `MessageAttachment` or
|
|
@@ -702,12 +713,18 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
702
713
|
reaches the chat as an attachment rather than the literal `[image]` it used to become. Persisting keeps each
|
|
703
714
|
attachment's name and drops its content: web storage is a few megabytes, one screenshot fills a chunk of it, and
|
|
704
715
|
a save that fails is silent — so keeping the bytes would quietly stop keeping the transcript.
|
|
716
|
+
**The ceilings are the message's, not the file's**: 4 MB per file, 8 MB and five files per message, and the same
|
|
717
|
+
file twice is refused by name. The bytes ride inside one turn's JSON, so what a provider refuses is the sum —
|
|
718
|
+
and a request that cannot be sent is one the user has to empty the composer to escape, which is why the refusal
|
|
719
|
+
happens at the paperclip and names the file it dropped.
|
|
705
720
|
- **Speech is one engine contract and the framework's own policy.** `<Agent.Chat voice={engine} />` takes a
|
|
706
721
|
`VoiceEngine` — `listen(handlers)` and `speak(sentence)`, both cancellable — and the chat decides everything
|
|
707
722
|
else: a press-to-talk microphone whose transcript lands in the composer to be corrected, one utterance per
|
|
708
723
|
press, sentence-at-a-time reading, barge-in on the next press or on Stop, and markdown stripped so `**bold**`
|
|
709
724
|
is not pronounced. **A reply is read aloud only when the ask arrived by voice**, so a typed question never turns
|
|
710
725
|
on the speakers — and it needs no wire field, because how a message was sent is the composer's own business.
|
|
726
|
+
A question or an approval the loop parked on is read aloud under that same condition, because the loop stops
|
|
727
|
+
there: a voice user who is never told about the card is a conversation that simply ends.
|
|
711
728
|
The contract is a subscription rather than `listen(): Promise<string>` on purpose: a promise fits push-to-talk
|
|
712
729
|
and nothing else, so hands-free could then only arrive as a breaking change. `useSpeech` in
|
|
713
730
|
`libs/util/webkit` is the engine — the browser's own recognition and synthesis on the web, the Capacitor
|
|
@@ -896,17 +913,20 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
896
913
|
read unless a `mask:` model is named — the same rule and wording as `AgentBridge.read`.
|
|
897
914
|
- **`prompt()` endpoints double as the chat's slash commands.** There is no listing endpoint — the client reads
|
|
898
915
|
its own serialized signals — so a prompt's dictionary `.desc()` is what the menu shows, and its guards are
|
|
899
|
-
enforced by the prompt's own GET at call time.
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
916
|
+
enforced by the prompt's own GET at call time. Arguments are positional and whitespace-separated, and quoting
|
|
917
|
+
is how a sentence stays one of them (`/reviewTask t1 "look at the totals"`) — a prompt taking a single `String`
|
|
918
|
+
is the common case, and an unquoted sentence would fill its second parameter with the second word.
|
|
919
|
+
- **The chat answers six slash commands of its own**, listed in the same `/` menu ahead of the prompts:
|
|
920
|
+
`/new` (`/clear`), `/retry`, `/compact`, `/copy`, `/help` and `/tools`. An app writes none of them and cannot add
|
|
921
|
+
one — the extension point for a product's own command is a `prompt()` endpoint, which is guarded and server-side.
|
|
903
922
|
**A built-in wins a name collision with a prompt of the same name**, the mirror image of the tool rule: a
|
|
904
923
|
component's `st.tool` shadows a built-in it means to replace, but no library's prompt may take `/new` away from
|
|
905
924
|
the user who typed it — so a shadowed prompt is dropped from the menu rather than listed twice. `/new` and
|
|
906
|
-
`/copy` are also dispatched *before* the is-a-turn-running check
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
925
|
+
`/copy` are also dispatched *before* the is-a-turn-running check **and before the question card takes the
|
|
926
|
+
composer**, because mid-turn is exactly when they are reached for and a question the agent asked is the middle
|
|
927
|
+
of a turn like any other — answered as text, `/new` would have reached the model as the user's decision.
|
|
928
|
+
`/new` therefore aborts the turn it is clearing and waits for it to wind down, since the loop clears its own
|
|
929
|
+
running flag a microtask later and a transcript emptied before that lands is one the dying turn appends onto.
|
|
910
930
|
- **A command's output is a `local` message: rendered in the transcript, withheld from the wire.** The transcript
|
|
911
931
|
*is* the model's history, so `/help` text appended plainly would come back next turn as something the assistant
|
|
912
932
|
believes it said. `session.note(text)` is the only way to write one, `session.report(error)` stays what a
|
|
@@ -916,8 +936,38 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
916
936
|
only in that browser, so an export is the one path a wrong answer has to whoever could fix it — which is why it
|
|
917
937
|
carries the route and the timestamp. `/retry` replays only the trailing user message, leaving anything before it
|
|
918
938
|
in place, so a prompt's own preamble is not sent twice.
|
|
919
|
-
-
|
|
920
|
-
|
|
939
|
+
- **A long conversation summarizes itself, because nothing else is keeping it inside the model's window.** The
|
|
940
|
+
loop runs in the browser and the relay holds no session, so an uncompacted chat grows until the provider
|
|
941
|
+
refuses the whole request — a refusal, never a shorter answer, which is why compaction runs *before* the turn
|
|
942
|
+
that would have overflowed rather than as a recovery after it. Past `compact.at` estimated tokens (four
|
|
943
|
+
characters to a token, over the JSON the turn posts; 24k by default, well under the smallest window a provider
|
|
944
|
+
is likely to have, since the tools and the screen context ride on top of it and neither compacts) the history above the last `keep` messages
|
|
945
|
+
becomes one message standing in for it, flagged `summary` on the wire — `<Agent.Chat compact={{ at, keep }} />`
|
|
946
|
+
tunes it per provider and `{ at: 0 }` turns it off, and `/compact` does the same on demand keeping nothing.
|
|
947
|
+
**The cut only ever lands on a user message**: everything above one is settled, so the kept half can never open
|
|
948
|
+
with a `tool` result whose call was summarized away, a shape every provider dialect rejects. The summarizing
|
|
949
|
+
turn carries no tools and no screen context — it summarizes the conversation, it does not act on it — and it is
|
|
950
|
+
fed a *bounded* digest rather than the transcript itself, since the transcript being summarized is the one that
|
|
951
|
+
no longer fits. A summary that cannot be produced leaves the transcript alone and the turn goes out as it would
|
|
952
|
+
have; one that fails to shrink anything is not retried until another threshold's worth has been added. On the
|
|
953
|
+
wire a summary wears the user's role because the wire has no other, so a provider mapping frames it as a system
|
|
954
|
+
message and `/retry` steps over it — replaying it would send the notes back as a question.
|
|
955
|
+
- **A stopped turn answers the calls it never ran, because an unanswered call ends the conversation.** Every
|
|
956
|
+
provider dialect refuses an assistant message whose `tool_calls` have no results — on that turn and on every
|
|
957
|
+
later one — so Stop landing between a call and its result would leave a transcript nothing can be sent from,
|
|
958
|
+
with no way out but `/new`. `Transcript.sanitize` holds the invariant in one place and runs where a transcript
|
|
959
|
+
is assembled rather than where each hole is made: the turn's own request, a transcript restored from storage,
|
|
960
|
+
and a stored transcript capped to its newest messages, whose window can start mid-pair. A call the loop never
|
|
961
|
+
reached is *answered* rather than erased — a model told the call was stopped asks again, where one shown no
|
|
962
|
+
call at all answers as if it had the result.
|
|
963
|
+
- **A turn that failed says so on the wire.** `error` is a field only this wire has, so a provider mapping reads
|
|
964
|
+
`text` and drops it; `AgentService.explained` folds it into the text before any adaptor sees it, because an
|
|
965
|
+
assistant turn that says nothing is one the model repeats.
|
|
966
|
+
- **↑ and ↓ in the composer walk what was sent**, seeded from the transcript so a persisted chat does not lose
|
|
967
|
+
only what was just typed. A single-line input has nothing of its own on the vertical arrows, and the
|
|
968
|
+
half-written draft they were walked away from comes back at the bottom of the walk. **The `/` menu takes those
|
|
969
|
+
keys while it is open** — it is the thing on screen the arrows point at — with Enter picking the highlighted
|
|
970
|
+
row, Tab completing its name, and Escape closing the menu and then, pressed again, the panel.
|
|
921
971
|
- The framework publishes six built-ins on every store surface: `navigate` (internal paths only, the same
|
|
922
972
|
router `Link` rides), `goBack` (this session's history — global, because history is not a control a page owns and
|
|
923
973
|
a page that draws no back link is not one you may not leave), `readScreen` (the rendered DOM as compact text —
|
|
@@ -939,12 +989,13 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
939
989
|
`maxTurns` budget in seconds on a job measured in minutes. Say so in the `desc` ("takes about two minutes; do
|
|
940
990
|
not poll while it runs") and, for a route full of slow work, in an `Agent.Guide`.
|
|
941
991
|
- **`waitFor(key, equals?, timeoutSeconds?)` is for the job the *tool* cannot await** — started in an earlier turn,
|
|
942
|
-
or by a person clicking the button. It parks on a
|
|
943
|
-
the moment the key moves; `equals` waits for one value, omitted it waits for
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
992
|
+
or by a person clicking the button. It parks on a **store state key a mounted component subscribes**, exactly the
|
|
993
|
+
set `readState` reads, and resumes the moment the key moves; `equals` waits for one value, omitted it waits for
|
|
994
|
+
any change. **Not a resource**: `st.expose` and `st.useState` register on the surface, whose values already ride
|
|
995
|
+
inline in the screen context block, and `waitFor` refuses one of those names like any other. Deliberately not a
|
|
996
|
+
bare sleep either — a sleep only makes the polling slower, and a value worth waiting minutes for is server-derived
|
|
997
|
+
state, which lives in the store already. Running out is not a failure, it answers with what the key holds now
|
|
998
|
+
(default 120s, clamped to 600), and a key this screen does not read is refused by name with the ones it does.
|
|
948
999
|
- **Stop reaches a tool that is still running.** The session races every call against its abort signal, so a
|
|
949
1000
|
two-minute tool does not hold the loop for two minutes after the user presses Stop. The signal itself arrives
|
|
950
1001
|
through `AgentAbort.current` — the same module slot `AgentProgress` is — and honouring it is optional, since the
|
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.43",
|
|
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.43",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"dayjs": "^1.11.20",
|