koneck 2.25.45 → 2.25.47

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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Permission questions waiting for an answer, oldest first.
3
+ *
4
+ * Sub-agents run concurrently and can each need an answer at the same moment. Holding the current
5
+ * question in a single slot meant the second overwrote the first, whose promise then never
6
+ * settled: that agent waited forever, the pool waiting on it never finished, and the turn hung
7
+ * with nothing on screen to explain why.
8
+ *
9
+ * A queue rather than a set of simultaneous prompts, because a permission question is only
10
+ * answerable if you can read it, and two at once on a terminal is neither.
11
+ */
12
+ export interface AskRequest<T> {
13
+ tool: string;
14
+ args: string;
15
+ prefix: string;
16
+ resolve: (answer: T) => void;
17
+ }
18
+ export declare class AskQueue<T> {
19
+ private readonly waiting;
20
+ /** Adds a question. Returns true when it is the one now on screen. */
21
+ add(request: AskRequest<T>): boolean;
22
+ /** The question that should be shown, or undefined when there are none. */
23
+ current(): AskRequest<T> | undefined;
24
+ /** How many are behind the current one. */
25
+ get pending(): number;
26
+ get size(): number;
27
+ /**
28
+ * Answers the current question and returns the next.
29
+ *
30
+ * Every exit from the panel goes through here, so no promise is left unsettled — one that is
31
+ * stalls the agent waiting on it, and anything waiting on that agent, indefinitely.
32
+ */
33
+ answer(value: T): AskRequest<T> | undefined;
34
+ /**
35
+ * Answers everything still waiting with the same value.
36
+ *
37
+ * For a cancelled turn: the agents are going away, and leaving their promises hanging would
38
+ * keep the pool alive after the work it was doing had been abandoned.
39
+ */
40
+ drain(value: T): number;
41
+ }
42
+ //# sourceMappingURL=ask-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-queue.d.ts","sourceRoot":"","sources":["../src/ask-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,sEAAsE;IACtE,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO;IAKpC,2EAA2E;IAC3E,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS;IAIpC,2CAA2C;IAC3C,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS;IAM3C;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM;CAKxB"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Permission questions waiting for an answer, oldest first.
3
+ *
4
+ * Sub-agents run concurrently and can each need an answer at the same moment. Holding the current
5
+ * question in a single slot meant the second overwrote the first, whose promise then never
6
+ * settled: that agent waited forever, the pool waiting on it never finished, and the turn hung
7
+ * with nothing on screen to explain why.
8
+ *
9
+ * A queue rather than a set of simultaneous prompts, because a permission question is only
10
+ * answerable if you can read it, and two at once on a terminal is neither.
11
+ */
12
+ export class AskQueue {
13
+ waiting = [];
14
+ /** Adds a question. Returns true when it is the one now on screen. */
15
+ add(request) {
16
+ this.waiting.push(request);
17
+ return this.waiting.length === 1;
18
+ }
19
+ /** The question that should be shown, or undefined when there are none. */
20
+ current() {
21
+ return this.waiting[0];
22
+ }
23
+ /** How many are behind the current one. */
24
+ get pending() {
25
+ return Math.max(0, this.waiting.length - 1);
26
+ }
27
+ get size() {
28
+ return this.waiting.length;
29
+ }
30
+ /**
31
+ * Answers the current question and returns the next.
32
+ *
33
+ * Every exit from the panel goes through here, so no promise is left unsettled — one that is
34
+ * stalls the agent waiting on it, and anything waiting on that agent, indefinitely.
35
+ */
36
+ answer(value) {
37
+ const current = this.waiting.shift();
38
+ current?.resolve(value);
39
+ return this.waiting[0];
40
+ }
41
+ /**
42
+ * Answers everything still waiting with the same value.
43
+ *
44
+ * For a cancelled turn: the agents are going away, and leaving their promises hanging would
45
+ * keep the pool alive after the work it was doing had been abandoned.
46
+ */
47
+ drain(value) {
48
+ const count = this.waiting.length;
49
+ while (this.waiting.length)
50
+ this.waiting.shift()?.resolve(value);
51
+ return count;
52
+ }
53
+ }
54
+ //# sourceMappingURL=ask-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-queue.js","sourceRoot":"","sources":["../src/ask-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,MAAM,OAAO,QAAQ;IACF,OAAO,GAAyB,EAAE,CAAC;IAEpD,sEAAsE;IACtE,GAAG,CAAC,OAAsB;QACxB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,2EAA2E;IAC3E,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,2CAA2C;IAC3C,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAQ;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAQ;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAClC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACjE,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -42,4 +42,17 @@ export declare function looksLikeContextOverflow(err: unknown): boolean;
42
42
  * repeating the same failure.
43
43
  */
44
44
  export declare function parseContextLimit(err: unknown): number | null;
45
+ /**
46
+ * The context window as the provider itself reports it.
47
+ *
48
+ * A table of model names is a guess, and a gateway makes it a bad one: `auto/best-coding` is an
49
+ * alias that resolves to whatever is best today, has a 1,048,576-token window, and matches
50
+ * nothing in any table. Assuming 32,768 for it compacted a conversation with 97% of its room
51
+ * left — sixty-nine messages summarised away for nothing, on a real run.
52
+ *
53
+ * An OpenAI-compatible /models listing usually carries the real number, and asking costs one
54
+ * request per session. A provider that does not answer, or answers without it, leaves the table
55
+ * in charge.
56
+ */
57
+ export declare function fetchModelContextLimit(baseURL: string, apiKey: string | undefined, model: string, timeoutMs?: number): Promise<number | null>;
45
58
  //# sourceMappingURL=context-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-limit.d.ts","sourceRoot":"","sources":["../src/context-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6BH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,uCAAuC;AACvC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,OAAO,CAAC;AAE/B,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI9D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgB7D"}
1
+ {"version":3,"file":"context-limit.d.ts","sourceRoot":"","sources":["../src/context-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6BH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,uCAAuC;AACvC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,OAAO,CAAC;AAE/B,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI9D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgB7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,SAAS,SAAQ,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBxB"}
@@ -104,4 +104,41 @@ export function parseContextLimit(err) {
104
104
  }
105
105
  return null;
106
106
  }
107
+ /**
108
+ * The context window as the provider itself reports it.
109
+ *
110
+ * A table of model names is a guess, and a gateway makes it a bad one: `auto/best-coding` is an
111
+ * alias that resolves to whatever is best today, has a 1,048,576-token window, and matches
112
+ * nothing in any table. Assuming 32,768 for it compacted a conversation with 97% of its room
113
+ * left — sixty-nine messages summarised away for nothing, on a real run.
114
+ *
115
+ * An OpenAI-compatible /models listing usually carries the real number, and asking costs one
116
+ * request per session. A provider that does not answer, or answers without it, leaves the table
117
+ * in charge.
118
+ */
119
+ export async function fetchModelContextLimit(baseURL, apiKey, model, timeoutMs = 6_000) {
120
+ try {
121
+ const response = await fetch(`${baseURL.replace(/\/$/, '')}/models`, {
122
+ headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {},
123
+ signal: AbortSignal.timeout(timeoutMs),
124
+ });
125
+ if (!response.ok)
126
+ return null;
127
+ const body = await response.json();
128
+ const entry = body.data?.find(m => m['id'] === model);
129
+ if (!entry)
130
+ return null;
131
+ // Different gateways name it differently; the input limit is the one that matters, since it
132
+ // is the prompt that has to fit.
133
+ for (const key of ['max_input_tokens', 'context_length', 'context_window', 'max_context_length']) {
134
+ const value = Number(entry[key]);
135
+ if (Number.isFinite(value) && value >= 1_000)
136
+ return Math.floor(value);
137
+ }
138
+ return null;
139
+ }
140
+ catch {
141
+ return null; // unreachable, slow, or not an OpenAI-shaped listing
142
+ }
143
+ }
107
144
  //# sourceMappingURL=context-limit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-limit.js","sourceRoot":"","sources":["../src/context-limit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-limit.js","sourceRoot":"","sources":["../src/context-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,wEAAwE;AACxE,MAAM,YAAY,GAA6C;IAC7D,CAAC,eAAe,EAAE,MAAM,CAAC;IACzB,CAAC,aAAa,EAAE,MAAM,CAAC;IACvB,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,aAAa,EAAE,MAAM,CAAC;IACvB,CAAC,UAAU,EAAE,MAAM,CAAC;IACpB,CAAC,QAAQ,EAAE,KAAK,CAAC;IACjB,CAAC,OAAO,EAAE,KAAK,CAAC;IAChB,CAAC,WAAW,EAAE,MAAM,CAAC;IACrB,CAAC,QAAQ,EAAE,KAAK,CAAC;IACjB,CAAC,SAAS,EAAE,MAAM,CAAC;IACnB,CAAC,KAAK,EAAE,MAAM,CAAC;IACf,CAAC,QAAQ,EAAE,OAAO,CAAC;IACnB,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,iBAAiB,EAAE,OAAO,CAAC;IAC5B,CAAC,iBAAiB,EAAE,OAAO,CAAC;IAC5B,CAAC,cAAc,EAAE,OAAO,CAAC;IACzB,CAAC,aAAa,EAAE,OAAO,CAAC;IACxB,CAAC,QAAQ,EAAE,OAAO,CAAC;IACnB,CAAC,QAAQ,EAAE,SAAS,CAAC;IACrB,CAAC,MAAM,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C,uCAAuC;AACvC,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,UAAmB;IAChE,IAAI,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/F,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,IAAI,GAA+C,IAAI,CAAC;IAC5D,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,SAAS;QACvC,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,IAAI,EAAE,KAAK,IAAI,qBAAqB,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC;AAE/B,MAAM,UAAU,aAAa,CAAC,YAAoB,EAAE,KAAa;IAC/D,OAAO,YAAY,GAAG,CAAC,IAAI,YAAY,IAAI,KAAK,GAAG,UAAU,CAAC;AAChE,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,wBAAwB,CAAC,GAAY;IACnD,MAAM,IAAI,GAAG,MAAM,CAAE,GAA4B,EAAE,OAAO,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACvF,OAAO,4HAA4H;SAChI,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAE,GAA4B,EAAE,OAAO,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG;QACf,wCAAwC;QACxC,gCAAgC;QAChC,yCAAyC;QACzC,4CAA4C;KAC7C,CAAC;IACF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QACjD,mFAAmF;QACnF,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC7D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAAe,EACf,MAA0B,EAC1B,KAAa,EACb,SAAS,GAAG,KAAK;IAEjB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,EAAE;YACnE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;YAC5D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAE9B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA+C,CAAC;QAChF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,4FAA4F;QAC5F,iCAAiC;QACjC,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,EAAE,CAAC;YACjG,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,KAAK;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAG,qDAAqD;IACtE,CAAC;AACH,CAAC"}
package/dist/engine.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import OpenAI from 'openai';
2
+ import type { Plan } from './plan.js';
2
3
  import { type PluginEntry } from './plugins.js';
3
4
  import { type MCPClientEntry } from './mcp.js';
4
5
  import { CheckpointRecorder } from './checkpoint.js';
@@ -161,12 +162,24 @@ export declare function mapWithLimit<T, R>(items: readonly T[], limit: number, f
161
162
  * in place would make every subsequent turn fail the same way.
162
163
  */
163
164
  export declare function stripImages(messages: Message[]): number;
164
- export declare function runReActLoop(messages: Message[], client: OpenAI, config: AgentConfig, effectiveCwd: string, stats: TokenStats, mcpClients: MCPClientEntry[], plugins: PluginEntry[], openaiTools: OpenAI.Chat.ChatCompletionTool[], turnOffset?: number, abortSignal?: AbortSignal, checkpoint?: CheckpointRecorder): Promise<boolean>;
165
+ export declare function runReActLoop(messages: Message[], client: OpenAI, config: AgentConfig, effectiveCwd: string, stats: TokenStats, mcpClients: MCPClientEntry[], plugins: PluginEntry[], openaiTools: OpenAI.Chat.ChatCompletionTool[], turnOffset?: number, abortSignal?: AbortSignal, checkpoint?: CheckpointRecorder,
166
+ /**
167
+ * The plan, held by the session rather than by this loop.
168
+ *
169
+ * It used to be declared here, and this runs once per user message — so saying "continue" wiped
170
+ * the plan and the model wrote a new one from nothing. A run that had done five of eleven steps
171
+ * started again at one of five, having forgotten both what it had done and what was left.
172
+ */
173
+ planRef?: {
174
+ current: Plan;
175
+ }): Promise<boolean>;
165
176
  export interface AgentSession {
166
177
  readonly id: string;
167
178
  readonly messages: Message[];
168
179
  readonly stats: TokenStats;
169
180
  readonly completed: boolean;
181
+ /** The plan as it stands, for saving with the session. */
182
+ readonly plan: Plan;
170
183
  /** `imagePaths` sends pictures explicitly; paths written in the text are attached anyway. */
171
184
  send(userText: string, signal?: AbortSignal, imagePaths?: readonly string[]): Promise<boolean>;
172
185
  }
@@ -187,7 +200,9 @@ export declare function createAgentSession(config: AgentConfig, initialMessages?
187
200
  * record of itself shrank each time it was resumed. `turnOffset` already reads `stats.turns`,
188
201
  * so seeding it also keeps turn numbering continuous instead of restarting at 1.
189
202
  */
190
- initialStats?: Partial<TokenStats>): Promise<AgentSession>;
203
+ initialStats?: Partial<TokenStats>,
204
+ /** A plan restored with a session, so resuming continues the work rather than re-planning it. */
205
+ initialPlan?: Plan): Promise<AgentSession>;
191
206
  /**
192
207
  * The session being continued, when `runAgent` is resuming one.
193
208
  *
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -9,9 +9,9 @@ import { tui } from './tui.js';
9
9
  import { isGitRepo, createWorktree, mergeWorktree } from './worktree.js';
10
10
  import { resolveProvider, getApiKey } from './providers.js';
11
11
  import { compressIfNeeded } from './compressor.js';
12
- import { contextLimitFor, looksLikeContextOverflow, parseContextLimit } from './context-limit.js';
12
+ import { contextLimitFor, looksLikeContextOverflow, parseContextLimit, fetchModelContextLimit, } from './context-limit.js';
13
13
  import { generateSessionId, saveSession } from './session.js';
14
- import { EMPTY_PLAN, parsePlan, planReminder, planAck } from './plan.js';
14
+ import { EMPTY_PLAN, parsePlan, planReminder, planAck, noPlanReminder } from './plan.js';
15
15
  import { resolveSandbox, describeSandbox } from './sandbox.js';
16
16
  import { loadImages, buildUserContent, imageMime, looksLikeVisionUnsupported, visionUnsupportedNotice, } from './images.js';
17
17
  import { parseTextToolCalls, paramTypesFrom, looksLikeTextToolCall, stripTemplateTokens, splitEmittable } from './tool-call-text.js';
@@ -156,7 +156,18 @@ export function isRetryableProviderError(err) {
156
156
  // Network-level failures carry no status.
157
157
  // "Connection error." is the OpenAI SDK's APIConnectionError wording and carries no status,
158
158
  // which is exactly the transient case retrying exists for. It cost a four-turn run.
159
- return /connection error|connection|timeout|timed out|socket|econnrese|epipe|enotfound|eai_again|fetch failed|stream idle|network|aborted/.test(text);
159
+ if (/connection error|connection|timeout|timed out|socket|econnrese|epipe|enotfound|eai_again|fetch failed|stream idle|network|aborted/.test(text)) {
160
+ return true;
161
+ }
162
+ // A stream that dies mid-flight, which a gateway reports in the body rather than the status.
163
+ // "Stream ended before producing a non-ping SSE event [model (502)] (bad_gateway)" arrived
164
+ // with no status at all and matched none of the above, so it was never retried — a run nine
165
+ // minutes in was ended by an upstream hiccup it should have shrugged off.
166
+ if (/stream ended|bad_gateway|bad gateway|service unavailable|overloaded|upstream|temporarily unavailable/.test(text)) {
167
+ return true;
168
+ }
169
+ // A 5xx named in the message rather than carried on the error.
170
+ return /\(5\d\d\)|\b5\d\d\b.*(gateway|server error|unavailable)/.test(text);
160
171
  }
161
172
  return false;
162
173
  }
@@ -514,6 +525,8 @@ export function resolveAgentConcurrency(requested) {
514
525
  return Math.max(1, Math.min(MAX_AGENT_CONCURRENCY, Math.floor(requested)));
515
526
  }
516
527
  /** Stagger between starts, so agents do not all issue their first request on the same tick. */
528
+ /** How much of a sub-agent's answer comes back. Enough for its conclusions, not its transcript. */
529
+ const SUBAGENT_REPORT_CHARS = 2_000;
517
530
  const AGENT_STAGGER_MS = 400;
518
531
  /**
519
532
  * Maps over items with at most `limit` running at once, preserving input order in the results.
@@ -535,7 +548,15 @@ export async function mapWithLimit(items, limit, fn) {
535
548
  await Promise.all(Array.from({ length: workers }, worker));
536
549
  return results;
537
550
  }
538
- async function handleSpawnAgents(args, config, cwd) {
551
+ async function handleSpawnAgents(args, config, cwd,
552
+ /**
553
+ * The parent's counters, so delegated work is counted as work.
554
+ *
555
+ * Sub-agent usage went nowhere: eight agents could spend a hundred thousand tokens between
556
+ * them and the session still reported only what the parent had spent itself. That is the
557
+ * figure people budget against, and it was wrong by however much was delegated.
558
+ */
559
+ parentStats) {
539
560
  const tasks = args.tasks.slice(0, 8);
540
561
  if (tasks.length === 0)
541
562
  return 'ERROR: tasks array is empty.';
@@ -555,6 +576,7 @@ async function handleSpawnAgents(args, config, cwd) {
555
576
  const concurrency = resolveAgentConcurrency(config.agentConcurrency);
556
577
  const results = await mapWithLimit(tasks, concurrency, async (task, i) => {
557
578
  const mine = progress[i];
579
+ let lastCounted = { prompt: 0, completion: 0, total: 0 };
558
580
  // Spread the opening requests over a moment. Three starting on the same tick is enough to
559
581
  // trip a per-second limit even when three concurrent is otherwise sustainable.
560
582
  if (i > 0)
@@ -587,6 +609,14 @@ async function handleSpawnAgents(args, config, cwd) {
587
609
  onToolResult: undefined,
588
610
  onAgentProgress: undefined,
589
611
  onTokens: (s) => {
612
+ // Reported cumulatively per agent, so the parent is given the difference.
613
+ if (parentStats) {
614
+ parentStats.promptTokens += Math.max(0, s.promptTokens - lastCounted.prompt);
615
+ parentStats.completionTokens += Math.max(0, s.completionTokens - lastCounted.completion);
616
+ parentStats.totalTokens += Math.max(0, s.totalTokens - lastCounted.total);
617
+ lastCounted = { prompt: s.promptTokens, completion: s.completionTokens, total: s.totalTokens };
618
+ config.onTokens?.(parentStats);
619
+ }
590
620
  mine.tokens = s.totalTokens;
591
621
  mine.turns = s.turns;
592
622
  publish();
@@ -603,8 +633,17 @@ async function handleSpawnAgents(args, config, cwd) {
603
633
  try {
604
634
  const session = await createAgentSession(subConfig);
605
635
  await session.send(task);
606
- const last = [...session.messages].reverse().find(m => m.role === 'assistant');
607
- const output = typeof last?.content === 'string' ? last.content.slice(0, 300) : '(completed)';
636
+ // The last assistant message with something in it — not merely the last, which after a
637
+ // tool call is often an empty shell — and enough of it to be worth having. Three hundred
638
+ // characters cut a sub-agent's conclusions mid-sentence, so the parent had to work out
639
+ // for itself what had been done, which is the opposite of delegating.
640
+ const said = [...session.messages].reverse().find(m => m.role === 'assistant' && typeof m.content === 'string' && m.content.trim().length > 0);
641
+ const full = typeof said?.content === 'string' ? said.content.trim() : '';
642
+ const output = full
643
+ ? (full.length > SUBAGENT_REPORT_CHARS
644
+ ? full.slice(0, SUBAGENT_REPORT_CHARS) + `\n … (${full.length - SUBAGENT_REPORT_CHARS} more characters)`
645
+ : full)
646
+ : '(finished without saying anything)';
608
647
  settle('done');
609
648
  return { task, output, success: true, branch };
610
649
  }
@@ -666,7 +705,15 @@ export function stripImages(messages) {
666
705
  }
667
706
  return removed;
668
707
  }
669
- export async function runReActLoop(messages, client, config, effectiveCwd, stats, mcpClients, plugins, openaiTools, turnOffset = 0, abortSignal, checkpoint) {
708
+ export async function runReActLoop(messages, client, config, effectiveCwd, stats, mcpClients, plugins, openaiTools, turnOffset = 0, abortSignal, checkpoint,
709
+ /**
710
+ * The plan, held by the session rather than by this loop.
711
+ *
712
+ * It used to be declared here, and this runs once per user message — so saying "continue" wiped
713
+ * the plan and the model wrote a new one from nothing. A run that had done five of eleven steps
714
+ * started again at one of five, having forgotten both what it had done and what was left.
715
+ */
716
+ planRef = { current: EMPTY_PLAN }) {
670
717
  const policy = await loadPolicy(effectiveCwd);
671
718
  const silent = Boolean(config.silent);
672
719
  const seenSignatures = new Set();
@@ -722,7 +769,6 @@ export async function runReActLoop(messages, client, config, effectiveCwd, stats
722
769
  * failing to reconstruct: by turn twelve it had read a great deal and could no longer say what
723
770
  * it had set out to do.
724
771
  */
725
- let plan = EMPTY_PLAN;
726
772
  /** True once tools have been taken away to force a final answer out of a looping run. */
727
773
  let forcedAnswer = false;
728
774
  let textToolsNoticed = false;
@@ -780,7 +826,11 @@ export async function runReActLoop(messages, client, config, effectiveCwd, stats
780
826
  // The plan travels with the request rather than being pushed into the history: appended to
781
827
  // the transcript it would scroll away behind the reading it is meant to survive, and a
782
828
  // dozen stale copies of it would be there competing with the current one.
783
- const reminder = planReminder(plan);
829
+ // A plan when there is one; a request for one when there is not and the job needs it.
830
+ // Silence is what produced sixty-three turns and a million tokens with no plan at all.
831
+ const reminder = planRef.current.steps.length > 0
832
+ ? planReminder(planRef.current)
833
+ : (turn >= 1 && !toolsDisabled ? noPlanReminder() : '');
784
834
  const outbound = reminder
785
835
  ? [...messages, { role: 'system', content: reminder }]
786
836
  : messages;
@@ -1285,9 +1335,9 @@ export async function runReActLoop(messages, client, config, effectiveCwd, stats
1285
1335
  result = next;
1286
1336
  }
1287
1337
  else {
1288
- plan = next;
1289
- config.onPlan?.(plan);
1290
- result = planAck(plan);
1338
+ planRef.current = next;
1339
+ config.onPlan?.(next);
1340
+ result = planAck(next);
1291
1341
  }
1292
1342
  }
1293
1343
  else if (tc.name === 'spawn_agents') {
@@ -1296,7 +1346,7 @@ export async function runReActLoop(messages, client, config, effectiveCwd, stats
1296
1346
  parsed = JSON.parse(tc.arguments);
1297
1347
  }
1298
1348
  catch { /* use empty */ }
1299
- result = await handleSpawnAgents(parsed, config, effectiveCwd);
1349
+ result = await handleSpawnAgents(parsed, config, effectiveCwd, stats);
1300
1350
  }
1301
1351
  else if (BUILTIN_NAMES.has(tc.name)) {
1302
1352
  result = await dispatchTool(tc.name, tc.arguments, effectiveCwd, config.requireApproval || policyDecision.requireApproval, line => config.onToolOutput?.(tc.name, line), sandbox);
@@ -1408,7 +1458,9 @@ export async function createAgentSession(config, initialMessages,
1408
1458
  * record of itself shrank each time it was resumed. `turnOffset` already reads `stats.turns`,
1409
1459
  * so seeding it also keeps turn numbering continuous instead of restarting at 1.
1410
1460
  */
1411
- initialStats) {
1461
+ initialStats,
1462
+ /** A plan restored with a session, so resuming continues the work rather than re-planning it. */
1463
+ initialPlan) {
1412
1464
  const client = buildClient(config);
1413
1465
  const id = generateSessionId();
1414
1466
  const stats = {
@@ -1441,6 +1493,18 @@ initialStats) {
1441
1493
  webSearch: detectSearchKey(),
1442
1494
  };
1443
1495
  const openaiTools = buildToolList(mcpClients, plugins, config, available);
1496
+ // Asked of the provider once, because a table of model names cannot know what a gateway alias
1497
+ // resolves to — and guessing small there summarised away most of a conversation that had room.
1498
+ // An explicit setting still wins; this only fills in what nobody has said.
1499
+ if (!config.contextTokens) {
1500
+ const reported = await fetchModelContextLimit(resolveProvider(config.provider, config.baseURL).baseURL, config.apiKey ?? getApiKey(resolveProvider(config.provider, config.baseURL)), config.model);
1501
+ if (reported) {
1502
+ config = { ...config, contextTokens: reported };
1503
+ if (!config.silent) {
1504
+ tui.printThinking(`${config.model} accepts ${reported.toLocaleString()} tokens of context.`);
1505
+ }
1506
+ }
1507
+ }
1444
1508
  // Resolved once. The writable area is the workspace plus anything named with --add-dir, and it
1445
1509
  // does not change part-way through a run.
1446
1510
  const sandbox = resolveSandbox(config.sandbox, config.cwd, config.addDirs ?? []);
@@ -1448,6 +1512,14 @@ initialStats) {
1448
1512
  tui.printThinking('Sandbox: ' + describeSandbox(sandbox));
1449
1513
  }
1450
1514
  let _completed = true;
1515
+ /**
1516
+ * The plan, for the life of the session rather than one message.
1517
+ *
1518
+ * Held here because a plan that dies with the turn is not a plan: a run five steps into eleven
1519
+ * was told "continue" and started again at one of five, having forgotten both what it had done
1520
+ * and what remained.
1521
+ */
1522
+ const planRef = { current: initialPlan ?? EMPTY_PLAN };
1451
1523
  async function send(userText, signal, imagePaths) {
1452
1524
  // Images named in the prompt are sent as images. Naming a screenshot used to do nothing at
1453
1525
  // all: the model got the path, could not open a PNG, and answered about a file it had never
@@ -1466,7 +1538,7 @@ initialStats) {
1466
1538
  // One checkpoint per user message: /revert undoes the file edits made in response to it.
1467
1539
  const checkpoint = new CheckpointRecorder(config.cwd, userText);
1468
1540
  try {
1469
- _completed = await runReActLoop(messages, client, { ...config, auditSessionId: id }, config.cwd, stats, mcpClients, plugins, openaiTools, turnOffset, signal, checkpoint);
1541
+ _completed = await runReActLoop(messages, client, { ...config, auditSessionId: id }, config.cwd, stats, mcpClients, plugins, openaiTools, turnOffset, signal, checkpoint, planRef);
1470
1542
  }
1471
1543
  finally {
1472
1544
  // Commit even when the turn threw or was interrupted — a partial edit is exactly
@@ -1480,6 +1552,7 @@ initialStats) {
1480
1552
  messages,
1481
1553
  stats,
1482
1554
  get completed() { return _completed; },
1555
+ get plan() { return planRef.current; },
1483
1556
  send,
1484
1557
  };
1485
1558
  }