agent-sh 0.12.3 → 0.12.5

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.
@@ -297,6 +297,18 @@ export class AgentLoop {
297
297
  recallArchiveSize: this.conversation.getRecallArchiveSize(),
298
298
  budgetTokens: this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
299
299
  }));
300
+ this.bus.onPipe("context:snapshot", (payload) => {
301
+ payload.messages = this.conversation.getMessages();
302
+ payload.contextWindow = this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
303
+ payload.activeTokens = this.conversation.estimateTokens();
304
+ return payload;
305
+ });
306
+ this.bus.onPipeAsync("context:compact", async (payload) => {
307
+ const stats = await this.compactWithHooks(0, undefined, false, payload.strategy);
308
+ if (stats)
309
+ payload.stats = { before: stats.before, after: stats.after, evictedCount: stats.evictedCount };
310
+ return payload;
311
+ });
300
312
  // Prior-session preamble (non-blocking). Both the read and the
301
313
  // layout go through advisable handlers.
302
314
  Promise.resolve(this.handlers.call("history:read-recent"))
@@ -469,11 +481,12 @@ export class AgentLoop {
469
481
  * compaction, emit `conversation:after-compact` so listeners
470
482
  * (metrics, UI, agent-awareness notes) can react.
471
483
  */
472
- compactWithHooks(target, keepRecent, force) {
484
+ compactWithHooks(target, keepRecent, force, strategy) {
473
485
  const stats = this.handlers.call("conversation:compact", {
474
486
  target,
475
487
  keepRecent,
476
488
  force: !!force,
489
+ strategy,
477
490
  });
478
491
  if (stats) {
479
492
  this.bus.emit("conversation:after-compact", {
@@ -819,7 +832,24 @@ export class AgentLoop {
819
832
  // Compaction strategy — default delegates to the two-tier pin
820
833
  // strategy in ConversationState; advisors replace wholesale.
821
834
  h.define("conversation:compact", (opts) => {
822
- return this.conversation.compact(opts.target, opts.keepRecent, opts.force);
835
+ const strategy = opts.strategy;
836
+ // Synthesize a CompactResult for manual edits so conversation:after-compact
837
+ // listeners (metrics, file-read cache, system-prompt cache) still run.
838
+ if (strategy?.kind === "rewind" || strategy?.kind === "replace") {
839
+ const before = this.conversation.estimatePromptTokens();
840
+ const beforeLen = this.conversation.getMessages().length;
841
+ const next = strategy.kind === "rewind"
842
+ ? this.conversation.getMessages().slice(0, strategy.toIndex)
843
+ : strategy.messages;
844
+ this.conversation.replaceMessages(next);
845
+ const after = this.conversation.estimatePromptTokens();
846
+ const afterLen = this.conversation.getMessages().length;
847
+ return { before, after, evictedCount: Math.max(0, beforeLen - afterLen) };
848
+ }
849
+ const tgt = strategy?.kind === "two-tier-pin" ? strategy.target : opts.target;
850
+ const keep = strategy?.kind === "two-tier-pin" ? strategy.keepRecent : opts.keepRecent;
851
+ const force = strategy?.kind === "two-tier-pin" ? strategy.force : opts.force;
852
+ return this.conversation.compact(tgt, keep, force);
823
853
  });
824
854
  // Inject a system note mid-loop — used by extensions (subagents,
825
855
  // peer messages) to deliver async results into the next iteration.
@@ -124,7 +124,10 @@ export class ContextManager {
124
124
  return null;
125
125
  const lastSeq = this.exchanges[this.exchanges.length - 1].id;
126
126
  // Outputs already carry head+tail+spillPath stubs from capture time.
127
- const body = fresh.map((ex) => this.formatExchangeTruncated(ex)).join("\n");
127
+ const parts = fresh.map((ex) => this.formatExchangeTruncated(ex)).filter((s) => s.length > 0);
128
+ if (parts.length === 0)
129
+ return null;
130
+ const body = parts.join("\n");
128
131
  return {
129
132
  text: `<shell-events>\n${body}</shell-events>`,
130
133
  lastSeq,
@@ -171,7 +174,9 @@ export class ContextManager {
171
174
  return s;
172
175
  }
173
176
  case "agent_query":
174
- return `#${ex.id} [you] > ${ex.query}\n`;
177
+ // Suppress: query already appears as the turn's user message.
178
+ // Kept in `exchanges` so search() can find it by id.
179
+ return "";
175
180
  }
176
181
  }
177
182
  formatExchangeFull(ex) {
@@ -233,6 +233,30 @@ export interface ShellEvents {
233
233
  totalTokens: number;
234
234
  budgetTokens: number;
235
235
  };
236
+ "context:snapshot": {
237
+ messages: unknown[];
238
+ contextWindow: number;
239
+ activeTokens: number;
240
+ };
241
+ "context:compact": {
242
+ strategy?: {
243
+ kind: "two-tier-pin";
244
+ target: number;
245
+ keepRecent?: number;
246
+ force?: boolean;
247
+ } | {
248
+ kind: "rewind";
249
+ toIndex: number;
250
+ } | {
251
+ kind: "replace";
252
+ messages: unknown[];
253
+ };
254
+ stats?: {
255
+ before: number;
256
+ after: number;
257
+ evictedCount: number;
258
+ };
259
+ };
236
260
  "agent:register-backend": {
237
261
  name: string;
238
262
  kill: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "main": "dist/core.js",