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.
- package/dist/ask-queue.d.ts +42 -0
- package/dist/ask-queue.d.ts.map +1 -0
- package/dist/ask-queue.js +54 -0
- package/dist/ask-queue.js.map +1 -0
- package/dist/context-limit.d.ts +13 -0
- package/dist/context-limit.d.ts.map +1 -1
- package/dist/context-limit.js +37 -0
- package/dist/context-limit.js.map +1 -1
- package/dist/engine.d.ts +17 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +88 -15
- package/dist/engine.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +56 -17
- package/dist/ink-chat.js.map +1 -1
- package/dist/plan.d.ts +10 -0
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +15 -0
- package/dist/plan.js.map +1 -1
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js.map +1 -1
- package/dist/tools.d.ts +3 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +34 -6
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|
package/dist/context-limit.d.ts
CHANGED
|
@@ -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"}
|
package/dist/context-limit.js
CHANGED
|
@@ -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":"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"}
|
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
|
|
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
|
|
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
|
*
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;
|
|
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
|
-
|
|
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
|
-
|
|
607
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1289
|
-
config.onPlan?.(
|
|
1290
|
-
result = planAck(
|
|
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
|
}
|