akanjs 3.0.0-alpha.43 → 3.0.0-alpha.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.43",
3
+ "version": "3.0.0-alpha.44",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -4,7 +4,6 @@ import { AgentBridge } from "./AgentBridge";
4
4
  import { ScreenReader } from "./ScreenReader";
5
5
  import { ScreenSettle } from "./ScreenSettle";
6
6
  import { ScreenTarget } from "./ScreenTarget";
7
- import { StateWait } from "./StateWait";
8
7
 
9
8
  /**
10
9
  * The tools every akan screen has whatever it declares: where it can go, what it is rendering, what one of the
@@ -39,7 +38,6 @@ export class StoreSurfaceSource implements SurfaceSource {
39
38
  StoreSurfaceSource.#goBack(),
40
39
  StoreSurfaceSource.#readScreen(viewKey),
41
40
  this.#readState(viewKey),
42
- this.#waitFor(viewKey),
43
41
  StoreSurfaceSource.#highlight(viewKey),
44
42
  ];
45
43
  this.#builtins.set(viewKey, builtins);
@@ -192,65 +190,6 @@ export class StoreSurfaceSource implements SurfaceSource {
192
190
  };
193
191
  }
194
192
 
195
- /**
196
- * The wait that costs no model turns.
197
- *
198
- * An agent that started something slow has one way to learn it finished: ask again, and again, a full round trip
199
- * per look. That burns the turn budget in seconds and reads to the user as a loop. This parks the call instead —
200
- * the session already awaits `run`, so the turn simply takes as long as the work does, and the change report
201
- * that follows carries whatever landed while it waited.
202
- *
203
- * It watches a published state key rather than sleeping for a fixed span. A bare sleep would only make the
204
- * polling slower, and a screen that can report progress at all reports it into the store; a screen that reports
205
- * none publishes a key with `st.expose`, which is worth doing anyway, since the key is then readable too.
206
- */
207
- #waitFor(viewKey: string): ToolEntry {
208
- return {
209
- name: "waitFor",
210
- description:
211
- "Wait here until one of this page's state keys settles, instead of asking again and again. Use it after starting something slow — a generation, an upload, a long job — whenever a state key reports how it is going: the turn pauses with no model round trip and resumes the moment the key moves. Keys are listed in the state context block. Returns what the key holds when the wait ends.",
212
- parameters: {
213
- type: "object",
214
- properties: {
215
- key: { type: "string", description: "The state key to watch, spelled as the state context block lists it." },
216
- equals: {
217
- type: "string",
218
- description:
219
- "Wait until the key reads exactly this, compared as text. Omit to wait until it changes from whatever it holds now.",
220
- },
221
- timeoutSeconds: {
222
- type: "number",
223
- description: `How long to wait before answering with the key's current value. Default ${StateWait.defaultSeconds}, maximum ${StateWait.maxSeconds}. Running out is not a failure — call waitFor again to keep waiting.`,
224
- },
225
- },
226
- required: ["key"],
227
- additionalProperties: false,
228
- },
229
-
230
- effect: "state",
231
- run: async (args) => {
232
- this.#bridge ??= AgentBridge.of();
233
- const key = typeof args.key === "string" ? args.key.trim() : "";
234
- if (!key) throw new Error("waitFor needs a key to watch.");
235
- const live = this.#bridge.readableKeys(viewKey);
236
- if (!live.includes(key))
237
- throw new Error(
238
- `No state key named ${key} is read by this screen. ${
239
- live.length
240
- ? `Keys here: ${live.join(", ")}.`
241
- : "This screen reads no state keys, so there is nothing to wait for."
242
- }`,
243
- );
244
- return await new StateWait(this.#bridge, {
245
- key,
246
- viewKey,
247
- equals: typeof args.equals === "string" ? args.equals : null,
248
- seconds: args.timeoutSeconds,
249
- }).run();
250
- },
251
- };
252
- }
253
-
254
193
  /**
255
194
  * The ring goes on once the scroll lands, not when it starts: a smooth scroll across a long page takes most of a
256
195
  * second, and a flash begun at the top is already fading by the time the user's eye arrives. Settles on the
@@ -7,7 +7,6 @@ export * from "./AgentPrompts";
7
7
  export * from "./ScreenReader";
8
8
  export * from "./ScreenSettle";
9
9
  export * from "./ScreenTarget";
10
- export * from "./StateWait";
11
10
  export * from "./StoreCatalogue";
12
11
  export * from "./StoreSurfaceSource";
13
12
  export * from "./storeSurface";
@@ -5,7 +5,6 @@ export * from "./AgentPrompts.d.ts";
5
5
  export * from "./ScreenReader.d.ts";
6
6
  export * from "./ScreenSettle.d.ts";
7
7
  export * from "./ScreenTarget.d.ts";
8
- export * from "./StateWait.d.ts";
9
8
  export * from "./StoreCatalogue.d.ts";
10
9
  export * from "./StoreSurfaceSource.d.ts";
11
10
  export * from "./storeSurface.d.ts";
@@ -18,8 +18,8 @@ const asked = (messages: readonly ChatMessage[], cap: number) =>
18
18
  export const useDraftRecall = (messages: readonly ChatMessage[]) => {
19
19
  const cap = 30;
20
20
  const sent = useRef<string[] | null>(null);
21
-
22
- const walked = (sent.current ??= asked(messages, cap));
21
+ if (!sent.current) sent.current = asked(messages, cap);
22
+ const walked = sent.current;
23
23
  const stashed = useRef("");
24
24
  const [at, setAt] = useState(0);
25
25
  return {
@@ -1,139 +0,0 @@
1
- import { AgentAbort, AgentProgress } from "../../vendor/use-agentic";
2
-
3
- /** The reading half of the store an in-page wait needs. `AgentBridge` is one; a test can be another. */
4
- export interface StateSource {
5
- read(key: string, viewKey?: string): unknown;
6
- subscribe(listener: () => void): () => void;
7
- }
8
-
9
- export interface StateWaitOptions {
10
- key: string;
11
- viewKey?: string;
12
- /** Settle when the key reads exactly this. Null waits for it to change from whatever it holds now. */
13
- equals?: string | null;
14
- /** Straight off the tool argument, so it may be anything; `StateWait.seconds` is what makes it a number. */
15
- seconds?: unknown;
16
- }
17
-
18
- /**
19
- * One `waitFor` call: park until a published state key settles, or until the timeout says how it is going.
20
- *
21
- * Two clocks, because neither covers the other. The store's own subscription catches the value changing, which is
22
- * the whole point of the tool and has to land immediately. The tick catches what the store never announces —
23
- * `retainLive` / `releaseLive` mutate the live-key map without notifying any listener, so a page navigated away
24
- * from mid-wait would otherwise hold the turn until the timeout — and the countdown row needs a tick anyway.
25
- *
26
- * Nothing here throws on a wait that ran out. A key still reading `generating` after two minutes is an answer, not
27
- * a failure, and the model is the one that decides whether to wait again.
28
- */
29
- export class StateWait {
30
- static readonly tickMs = 1000;
31
- static readonly defaultSeconds = 120;
32
- static readonly maxSeconds = 600;
33
-
34
- /** Clamped rather than refused: a model that asks for an hour gets the longest wait on offer and reads how long. */
35
- static seconds(value: unknown): number {
36
- if (typeof value !== "number" || !Number.isFinite(value)) return StateWait.defaultSeconds;
37
- return Math.min(Math.max(Math.round(value), 1), StateWait.maxSeconds);
38
- }
39
-
40
- /** A string is itself; everything else is JSON, so a number, a boolean and null each compare as they are written. */
41
- static print(value: unknown): string {
42
- if (typeof value === "string") return value;
43
- try {
44
- return JSON.stringify(value) ?? String(value);
45
- } catch {
46
- return String(value);
47
- }
48
- }
49
-
50
- readonly #source: StateSource;
51
- readonly #key: string;
52
- readonly #viewKey: string;
53
- readonly #equals: string | null;
54
- readonly #seconds: number;
55
- #was = "";
56
- #now = "";
57
- #elapsed = 0;
58
- #settled: boolean = false;
59
- #ticker: ReturnType<typeof setInterval> | undefined;
60
- #unsubscribe: (() => void) | undefined;
61
- #signal: AbortSignal | null = null;
62
- #resolve: ((message: string) => void) | null = null;
63
- #reject: ((error: Error) => void) | null = null;
64
-
65
- constructor(source: StateSource, { key, viewKey = "", equals = null, seconds }: StateWaitOptions) {
66
- this.#source = source;
67
- this.#key = key;
68
- this.#viewKey = viewKey;
69
- this.#equals = equals;
70
- this.#seconds = StateWait.seconds(seconds);
71
- }
72
-
73
- run(): Promise<string> {
74
- this.#was = StateWait.print(this.#source.read(this.#key, this.#viewKey));
75
- this.#now = this.#was;
76
- if (this.#equals !== null && this.#was === this.#equals)
77
- return Promise.resolve(`${this.#key} is already ${this.#was}.`);
78
- return new Promise<string>((resolve, reject) => {
79
- this.#resolve = resolve;
80
- this.#reject = reject;
81
- this.#signal = AgentAbort.current;
82
- if (this.#signal?.aborted) {
83
- this.#abort();
84
- return;
85
- }
86
- this.#signal?.addEventListener("abort", this.#abort);
87
- this.#unsubscribe = this.#source.subscribe(this.#check);
88
- this.#ticker = setInterval(this.#tick, StateWait.tickMs);
89
- AgentProgress.report("", { done: 0, total: this.#seconds });
90
- });
91
- }
92
-
93
- /** The session races every call against the same signal; honouring it here is what stops the timer. */
94
- #abort = () => {
95
- this.#stop();
96
- this.#reject?.(new Error("The user aborted the turn."));
97
- };
98
-
99
- #check = () => {
100
- if (this.#settled) return;
101
- let now: string;
102
- try {
103
- now = StateWait.print(this.#source.read(this.#key, this.#viewKey));
104
- } catch (error) {
105
-
106
- this.#end(`Stopped waiting: ${error instanceof Error ? error.message : String(error)}`);
107
- return;
108
- }
109
- this.#now = now;
110
- if (this.#equals === null ? now !== this.#was : now === this.#equals) this.#end(`${this.#key} is now ${now}.`);
111
- };
112
-
113
- #tick = () => {
114
- this.#check();
115
- if (this.#settled) return;
116
- this.#elapsed += 1;
117
- if (this.#elapsed >= this.#seconds) {
118
- this.#end(
119
- `${this.#key} is still ${this.#now} after ${this.#seconds}s. Call waitFor again to keep waiting, or tell the user it is taking longer than expected.`,
120
- );
121
- return;
122
- }
123
- AgentProgress.report("", { done: this.#elapsed, total: this.#seconds });
124
- };
125
-
126
- #end(message: string) {
127
- this.#stop();
128
- this.#resolve?.(message);
129
- }
130
-
131
- #stop() {
132
- this.#settled = true;
133
- clearInterval(this.#ticker);
134
- this.#ticker = undefined;
135
- this.#unsubscribe?.();
136
- this.#unsubscribe = undefined;
137
- this.#signal?.removeEventListener("abort", this.#abort);
138
- }
139
- }
@@ -1,36 +0,0 @@
1
- /** The reading half of the store an in-page wait needs. `AgentBridge` is one; a test can be another. */
2
- export interface StateSource {
3
- read(key: string, viewKey?: string): unknown;
4
- subscribe(listener: () => void): () => void;
5
- }
6
- export interface StateWaitOptions {
7
- key: string;
8
- viewKey?: string;
9
- /** Settle when the key reads exactly this. Null waits for it to change from whatever it holds now. */
10
- equals?: string | null;
11
- /** Straight off the tool argument, so it may be anything; `StateWait.seconds` is what makes it a number. */
12
- seconds?: unknown;
13
- }
14
- /**
15
- * One `waitFor` call: park until a published state key settles, or until the timeout says how it is going.
16
- *
17
- * Two clocks, because neither covers the other. The store's own subscription catches the value changing, which is
18
- * the whole point of the tool and has to land immediately. The tick catches what the store never announces —
19
- * `retainLive` / `releaseLive` mutate the live-key map without notifying any listener, so a page navigated away
20
- * from mid-wait would otherwise hold the turn until the timeout — and the countdown row needs a tick anyway.
21
- *
22
- * Nothing here throws on a wait that ran out. A key still reading `generating` after two minutes is an answer, not
23
- * a failure, and the model is the one that decides whether to wait again.
24
- */
25
- export declare class StateWait {
26
- #private;
27
- static readonly tickMs = 1000;
28
- static readonly defaultSeconds = 120;
29
- static readonly maxSeconds = 600;
30
- /** Clamped rather than refused: a model that asks for an hour gets the longest wait on offer and reads how long. */
31
- static seconds(value: unknown): number;
32
- /** A string is itself; everything else is JSON, so a number, a boolean and null each compare as they are written. */
33
- static print(value: unknown): string;
34
- constructor(source: StateSource, { key, viewKey, equals, seconds }: StateWaitOptions);
35
- run(): Promise<string>;
36
- }