agent.libx.js 0.94.17 → 0.94.19

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.
@@ -1,5 +1,5 @@
1
1
  import { IFilesystem } from '@livx.cc/wcli/core';
2
- import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, e as MessageContent } from './tools-GPWp7oXq.js';
2
+ import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, e as MessageContent } from './tools-9AUK6SG2.js';
3
3
 
4
4
  /**
5
5
  * Hooks — deterministic interception points around tool execution, run by the
@@ -209,7 +209,8 @@ declare class AgentOptions {
209
209
  systemPrompt: string;
210
210
  tools: AgentTool[];
211
211
  maxSteps: number;
212
- /** Hard ceiling on accumulated tokens (prompt+completion) across the run. 0 = unbounded. */
212
+ /** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.
213
+ * (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
213
214
  maxTokens: number;
214
215
  /** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
215
216
  timeoutMs: number;
@@ -317,6 +318,10 @@ declare class Agent {
317
318
  private prepared;
318
319
  private systemPromptCache;
319
320
  private started;
321
+ private parkedMs;
322
+ /** Time a human-blocking await (a permission/plan prompt) and bank it in `parkedMs` so idle prompt
323
+ * time never trips the wall-clock kill-switch. The agent did no work while parked on the user. */
324
+ private park;
320
325
  /** Force the next `send()`/`run()` to rebuild the system prompt, tools, plan-mode and permission hooks
321
326
  * from `options` — apply mid-conversation changes to `planMode`/`permissions`/`model` etc. (prepare()
322
327
  * is otherwise memoized per conversation). */
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { h as RunResult, R as ReasoningEffort } from './Agent-COa80xYy.js';
2
+ import { h as RunResult, R as ReasoningEffort } from './Agent-DmsB5hzp.js';
3
3
  import { IFilesystem } from '@livx.cc/wcli/core';
4
- import { M as Message, c as ContentPart } from './tools-GPWp7oXq.js';
4
+ import { M as Message, c as ContentPart } from './tools-9AUK6SG2.js';
5
5
 
6
6
  /**
7
7
  * On-disk session store for the CLI: each conversation is one JSON file at
package/dist/cli.js CHANGED
@@ -2590,9 +2590,10 @@ function planMode(opts) {
2590
2590
  parameters: { type: "object", required: ["plan"], properties: { plan: { type: "string", description: "the concrete steps you will take" } } },
2591
2591
  async run({ plan }, _ctx) {
2592
2592
  if (opts?.host?.confirm) {
2593
- const ok = await opts.host.confirm(`Approve this plan?
2593
+ const confirm = opts.host.confirm(`Approve this plan?
2594
2594
 
2595
2595
  ${String(plan ?? "")}`);
2596
+ const ok = await (_ctx?.parkHuman ? _ctx.parkHuman(confirm) : confirm);
2596
2597
  if (!ok) return "Plan not approved. Revise it and call ExitPlanMode again.";
2597
2598
  }
2598
2599
  approved = true;
@@ -2748,7 +2749,8 @@ var AgentOptions = class {
2748
2749
  tools = defaultTools();
2749
2750
  maxSteps = 25;
2750
2751
  // --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
2751
- /** Hard ceiling on accumulated tokens (prompt+completion) across the run. 0 = unbounded. */
2752
+ /** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.
2753
+ * (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
2752
2754
  maxTokens = 2e5;
2753
2755
  /** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
2754
2756
  timeoutMs = 12e4;
@@ -2850,6 +2852,18 @@ var Agent = class _Agent {
2850
2852
  // the assembled system prompt from the last prepare()
2851
2853
  started = false;
2852
2854
  // session-start lifecycle hook fires once per conversation
2855
+ parkedMs = 0;
2856
+ // cumulative time blocked on the HUMAN (permission/plan prompts) — excluded from the timeout
2857
+ /** Time a human-blocking await (a permission/plan prompt) and bank it in `parkedMs` so idle prompt
2858
+ * time never trips the wall-clock kill-switch. The agent did no work while parked on the user. */
2859
+ async park(p) {
2860
+ const t = Date.now();
2861
+ try {
2862
+ return await p;
2863
+ } finally {
2864
+ this.parkedMs += Date.now() - t;
2865
+ }
2866
+ }
2853
2867
  /** Force the next `send()`/`run()` to rebuild the system prompt, tools, plan-mode and permission hooks
2854
2868
  * from `options` — apply mid-conversation changes to `planMode`/`permissions`/`model` etc. (prepare()
2855
2869
  * is otherwise memoized per conversation). */
@@ -2878,6 +2892,7 @@ var Agent = class _Agent {
2878
2892
  if (this.options.lintOnWrite) this.ctx.lint = checkSyntax;
2879
2893
  this.ctx.ai = this.options.ai;
2880
2894
  this.ctx.model = this.options.model;
2895
+ this.ctx.parkHuman = (p) => this.park(p);
2881
2896
  if (this.options.backgroundJobs) this.ctx.jobs = new SandboxJobRegistry();
2882
2897
  }
2883
2898
  /**
@@ -3023,19 +3038,21 @@ var Agent = class _Agent {
3023
3038
  const usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
3024
3039
  let usageEstimated = false;
3025
3040
  const start = Date.now();
3041
+ this.parkedMs = 0;
3026
3042
  let toolCallsTotal = 0;
3027
3043
  let lastFp = "";
3028
3044
  let repeats = 0;
3029
3045
  const kill = (finishReason) => {
3030
- log3.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, ms=${Date.now() - start})`);
3046
+ log3.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
3031
3047
  this.ctx.jobs?.killAll();
3032
3048
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
3033
3049
  };
3034
3050
  while (true) {
3035
3051
  if (o.signal?.aborted) return kill("aborted");
3036
3052
  if (steps >= o.maxSteps) return kill("max_steps");
3037
- if (o.timeoutMs && Date.now() - start >= o.timeoutMs) return kill("timeout");
3038
- if (o.maxTokens && usage.totalTokens >= o.maxTokens) return kill("budget");
3053
+ if (o.timeoutMs && Date.now() - start - this.parkedMs >= o.timeoutMs) return kill("timeout");
3054
+ const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
3055
+ if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
3039
3056
  steps++;
3040
3057
  this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
3041
3058
  let res;
@@ -3170,7 +3187,7 @@ var Agent = class _Agent {
3170
3187
  const hooks = this.activeHooks;
3171
3188
  const call = { name: tc.function.name, args };
3172
3189
  const meta = { id: tc.id };
3173
- const decision = await hooks?.preToolUse?.(call, meta);
3190
+ const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
3174
3191
  if (decision?.block) {
3175
3192
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
3176
3193
  log3.debug(`${tc.function.name} -> ${blocked}`);
@@ -3212,6 +3229,7 @@ var Agent = class _Agent {
3212
3229
  this.ctx.emit = void 0;
3213
3230
  }
3214
3231
  if (!threw) result = await this.maybeAutoTest(tc.function.name, result);
3232
+ if (images?.length && !result) result = `[${images.length} image${images.length > 1 ? "s" : ""} attached]`;
3215
3233
  const cap = this.options.maxToolResultBytes ?? 0;
3216
3234
  if (!threw && cap > 0 && result.length > cap) {
3217
3235
  const info = { tool: tc.function.name, args };
@@ -7457,6 +7475,12 @@ var EditorState = class _EditorState {
7457
7475
  pasteChar(c) {
7458
7476
  this.pasteBuf += c;
7459
7477
  }
7478
+ /** Actively grab a clipboard image and attach it (Ctrl-V; same hook as the empty-paste path). */
7479
+ grabClipboard() {
7480
+ const att = this.onEmptyPaste?.();
7481
+ if (att) this.attach(att.display, att.ref);
7482
+ return !!att;
7483
+ }
7460
7484
  endPaste() {
7461
7485
  this.pasting = false;
7462
7486
  const text = this.pasteBuf;
@@ -7887,6 +7911,10 @@ function applyKey(s, key, str) {
7887
7911
  if (key?.ctrl && k === "u") return s.killToStart(), "none";
7888
7912
  if (key?.ctrl && k === "k") return s.killToEnd(), "none";
7889
7913
  if (key?.ctrl && k === "y") return s.yank(), "none";
7914
+ if (key?.ctrl && k === "v") {
7915
+ s.grabClipboard();
7916
+ return "none";
7917
+ }
7890
7918
  if (str === "") return s.undo(), "none";
7891
7919
  if (key?.meta && k === "b") return s.wordLeft(), "none";
7892
7920
  if (key?.meta && k === "f") return s.wordRight(), "none";
@@ -8774,7 +8802,7 @@ REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to co
8774
8802
  REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
8775
8803
  REPL stash: type while a turn is running \u2192 Enter queues it (auto-submits when the turn finishes). Alt+S (or Ctrl+S) with text stashes it; on an empty prompt pops the next entry for editing.
8776
8804
  REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
8777
- REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; /paste grabs a clipboard image (macOS).`;
8805
+ REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
8778
8806
  function newestModel() {
8779
8807
  return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
8780
8808
  }
@@ -10949,7 +10977,7 @@ ${extra}` : body);
10949
10977
  },
10950
10978
  init: { desc: "scaffold ./AGENTS.md project instructions", run: () => initInstructions(cwd) },
10951
10979
  paste: {
10952
- desc: "attach an image from the clipboard (macOS) \u2014 /paste [message] sends now, /paste alone attaches to your next message",
10980
+ desc: "attach an image from the clipboard (macOS, = Ctrl-V) \u2014 /paste [message] sends now, /paste alone attaches to your next message",
10953
10981
  run: async (a) => {
10954
10982
  const att = grabClipboardAttachment();
10955
10983
  if (!att) {