akanjs 3.0.0-alpha.47 → 3.0.0-alpha.49

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.
@@ -812,7 +812,9 @@ export class FetchClient {
812
812
  const handler = instance.#getOrCreateHandler(prop);
813
813
  if (handler) return handler;
814
814
  }
815
- return (instance as unknown as Record<PropertyKey, unknown>)[prop];
815
+ const value = (instance as unknown as Record<PropertyKey, unknown>)[prop];
816
+
817
+ return typeof value === "function" ? value.bind(instance) : value;
816
818
  },
817
819
  }) as FetchProxy<FetchType, SliceMetaObj>;
818
820
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.47",
3
+ "version": "3.0.0-alpha.49",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -43,7 +43,7 @@ export class SignalResolver {
43
43
  return `${key}${args.length ? "-" : ""}${args.join("-")}`;
44
44
  }
45
45
  static #localPublish: (roomId: string, data: object | object[]) => void = () => {
46
- SignalResolver.logger.warn(`Local publish is not initialized yet`);
46
+ SignalResolver.logger.verbose(`Local publish is not initialized yet`);
47
47
  };
48
48
  static setLocalPublish(localPublish: (roomId: string, data: object | object[]) => void, websocket: WebsocketAdaptor) {
49
49
  SignalResolver.#localPublish = localPublish;
@@ -2,7 +2,7 @@ import type { ChatMessage } from "./types.d.ts";
2
2
  export interface CompactOptions {
3
3
  /** Estimated transcript tokens above which a turn summarizes its own history first. `0` never compacts. */
4
4
  at?: number;
5
- /** Messages left verbatim below the summary. The cut rises to the nearest user message above them. */
5
+ /** Messages left verbatim below the summary. The cut slides down to the first message that can safely open one. */
6
6
  keep?: number;
7
7
  /** Produces the summary from the digest. Default: one tool-less turn through the session's own runner. */
8
8
  summarize?: (digest: string, signal: AbortSignal) => Promise<string>;
@@ -31,9 +31,12 @@ export declare class Compaction {
31
31
  */
32
32
  static tokensOf(messages: readonly ChatMessage[]): number;
33
33
  /**
34
- * Where the kept half starts, or `-1` when nothing can be cut. Only a user message opens one: everything before
35
- * it is settled, so the kept half never begins with a `tool` result whose call was summarized away — a shape
36
- * every provider dialect rejects. `keep: 0` summarizes the whole transcript, which is what the command does.
34
+ * Where the kept half starts, or `-1` when nothing can be cut. A user message is the boundary to prefer —
35
+ * everything above it is settled but one assistant turn that ran ten tools leaves no user message anywhere
36
+ * near the tail, and that is exactly the transcript that outgrows the window. What the pairing every provider
37
+ * dialect enforces actually requires is only that the kept half not open with a `tool` result whose call was
38
+ * summarized away, so any non-`tool` message opens one too. `keep: 0` summarizes the whole transcript, which is
39
+ * what the command does.
37
40
  */
38
41
  static cutAt(messages: readonly ChatMessage[], keep: number): number;
39
42
  /**
@@ -334,14 +334,17 @@ export class AgentSession {
334
334
  async #autoCompact(signal: AbortSignal) {
335
335
  const { at = Compaction.defaults.at, keep = Compaction.defaults.keep } = this.#options.compact ?? {};
336
336
  if (!at || Compaction.tokensOf(this.#messages) < Math.max(at, this.#compactFloor)) return;
337
+ let compacted = false;
337
338
  try {
338
- await this.#compact(keep, signal);
339
+ compacted = await this.#compact(keep, signal);
339
340
  } catch (error) {
341
+
340
342
  console.warn(`[use-agentic] compaction failed: ${error instanceof Error ? error.message : String(error)}`);
343
+ return;
341
344
  }
342
345
  const after = Compaction.tokensOf(this.#messages);
343
346
 
344
- this.#compactFloor = after < at ? 0 : after + at;
347
+ this.#compactFloor = compacted && after >= at ? after + at : 0;
345
348
  }
346
349
 
347
350
  /** No tools and no screen context: this turn summarizes the conversation, and must not act on it. */
@@ -3,7 +3,7 @@ import type { ChatMessage } from "./types";
3
3
  export interface CompactOptions {
4
4
  /** Estimated transcript tokens above which a turn summarizes its own history first. `0` never compacts. */
5
5
  at?: number;
6
- /** Messages left verbatim below the summary. The cut rises to the nearest user message above them. */
6
+ /** Messages left verbatim below the summary. The cut slides down to the first message that can safely open one. */
7
7
  keep?: number;
8
8
  /** Produces the summary from the digest. Default: one tool-less turn through the session's own runner. */
9
9
  summarize?: (digest: string, signal: AbortSignal) => Promise<string>;
@@ -40,16 +40,26 @@ export class Compaction {
40
40
  }
41
41
 
42
42
  /**
43
- * Where the kept half starts, or `-1` when nothing can be cut. Only a user message opens one: everything before
44
- * it is settled, so the kept half never begins with a `tool` result whose call was summarized away — a shape
45
- * every provider dialect rejects. `keep: 0` summarizes the whole transcript, which is what the command does.
43
+ * Where the kept half starts, or `-1` when nothing can be cut. A user message is the boundary to prefer —
44
+ * everything above it is settled but one assistant turn that ran ten tools leaves no user message anywhere
45
+ * near the tail, and that is exactly the transcript that outgrows the window. What the pairing every provider
46
+ * dialect enforces actually requires is only that the kept half not open with a `tool` result whose call was
47
+ * summarized away, so any non-`tool` message opens one too. `keep: 0` summarizes the whole transcript, which is
48
+ * what the command does.
46
49
  */
47
50
  static cutAt(messages: readonly ChatMessage[], keep: number): number {
48
51
  if (!messages.length) return -1;
49
52
  if (keep <= 0) return messages.length;
50
53
  const target = messages.length - keep;
51
54
  if (target <= 0) return -1;
52
- for (let at = target; at < messages.length; at += 1) if (messages[at].role === "user") return at;
55
+ let boundary = -1;
56
+ for (let at = target; at < messages.length; at += 1) {
57
+ if (messages[at].role === "user") return at;
58
+ if (boundary < 0 && messages[at].role !== "tool") boundary = at;
59
+ }
60
+ if (boundary >= 0) return boundary;
61
+
62
+ for (let at = target - 1; at > 0; at -= 1) if (messages[at].role !== "tool") return at;
53
63
  return -1;
54
64
  }
55
65