@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
- ebe6f07427135e1b11548007c742cfa1e62256965f0de2c84b3f816d97ef8688
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. Re-skin through the `AgentChat` slot in `_overrides.tsx`.
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
- - **The chat answers five slash commands of its own**, listed in the same `/` menu ahead of the prompts:
901
- `/new` (`/clear`), `/retry`, `/copy`, `/help` and `/tools`. An app writes none of them and cannot add one — the
902
- extension point for a product's own command is a `prompt()` endpoint, which is guarded and server-side.
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, because mid-turn is exactly when they are
907
- reached for; `/new` therefore aborts the turn it is clearing and waits for it to wind down, since the loop
908
- clears its own running flag a microtask later and a transcript emptied before that lands is one the dying turn
909
- appends onto.
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
- - **↑ and in the composer walk what was sent.** A single-line input has nothing of its own on the vertical
920
- arrows, and the half-written draft they were walked away from comes back at the bottom of the walk.
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 published state key, the ones `readState` lists, and resumes
943
- the moment the key moves; `equals` waits for one value, omitted it waits for any change. Deliberately not a bare
944
- sleep: a sleep only makes the polling slower, and a screen able to report progress reports it into the store, so
945
- a screen that reports none publishes a key with `st.expose` worth doing anyway, since the key becomes readable
946
- too. Running out is not a failure, it answers with what the key holds now (default 120s, clamped to 600), and a
947
- key this screen does not read is refused by name with the ones it does.
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.41",
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.41",
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",