koneck 2.95.0 → 2.96.0
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/capability-claims.d.ts +63 -0
- package/dist/capability-claims.d.ts.map +1 -0
- package/dist/capability-claims.js +171 -0
- package/dist/capability-claims.js.map +1 -0
- package/dist/context-limit.d.ts +14 -2
- package/dist/context-limit.d.ts.map +1 -1
- package/dist/context-limit.js +121 -11
- package/dist/context-limit.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +82 -14
- package/dist/engine.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web/sessions.d.ts.map +1 -1
- package/dist/web/sessions.js +3 -0
- package/dist/web/sessions.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -7,7 +7,7 @@ import { execa } from 'execa';
|
|
|
7
7
|
import { TOOL_DEFINITIONS, dispatchTool, describeShell, unknownToolError, resolveToolAlias, aliasNotice, isToolFailure, } from './tools.js';
|
|
8
8
|
import { tui } from './tui.js';
|
|
9
9
|
import { hasWorkspaceOwnedGitRepo, gitScope, createWorktree, mergeWorktree, preservedWorktreeNotice } from './worktree.js';
|
|
10
|
-
import { resolveProvider, getApiKey, keyProvenance, servesLocally, servesOwnWeights, needsNoCredential
|
|
10
|
+
import { resolveProvider, getApiKey, keyProvenance, servesLocally, servesOwnWeights, needsNoCredential } from './providers.js';
|
|
11
11
|
import { wireEffort, looksLikeEffortUnsupported, acceptedEfforts, nearestAccepted } from './pace.js';
|
|
12
12
|
import { triggerFor, pickNext, describeSwitch, askSwitch, keyOf as failoverKey } from './failover.js';
|
|
13
13
|
import { findChurn, describeChurn, WINDOW as CHURN_WINDOW } from './cycle.js';
|
|
@@ -17,7 +17,7 @@ import { handoffFrom, describeHandoff, worthSaying } from './handoff.js';
|
|
|
17
17
|
import { readAll as readLedger, relevant as relevantFindings, withStaleness, describeFindings, deriveFindings, record as recordFindings, changedSince, currentSha } from './ledger.js';
|
|
18
18
|
import { repoNameFor } from './session-bus.js';
|
|
19
19
|
import { compressIfNeeded } from './compressor.js';
|
|
20
|
-
import { contextLimitFor, looksLikeContextOverflow, parseContextLimit, fetchModelContextLimit, ollamaServingLimit, fixedRequestTokens, overheadIsCrowded, crowdedWindowAdvice, probeOllamaContext, detectWindowPlateau, truncationNotice, } from './context-limit.js';
|
|
20
|
+
import { contextLimitFor, looksLikeContextOverflow, parseContextLimit, fetchModelContextLimit, ollamaServingLimit, fixedRequestTokens, overheadIsCrowded, crowdedWindowAdvice, probeOllamaContext, detectWindowPlateau, truncationNotice, ollamaWindowFix, runsOnThisMachine, } from './context-limit.js';
|
|
21
21
|
import { generateSessionId, saveSession } from './session.js';
|
|
22
22
|
import { EMPTY_PLAN, parsePlan, planReminder, planAck, noPlanReminder } from './plan.js';
|
|
23
23
|
import { spillIfOversized } from './spill.js';
|
|
@@ -26,6 +26,7 @@ import { resolveSandbox, describeSandbox } from './sandbox.js';
|
|
|
26
26
|
import { loadImages, buildUserContent, imageMime, looksLikeVisionUnsupported, visionUnsupportedNotice, } from './images.js';
|
|
27
27
|
import { parseTextToolCalls, paramTypesFrom, looksLikeTextToolCall, stripTemplateTokens, splitEmittable, parseCommandStyleCalls, toolShapesFrom } from './tool-call-text.js';
|
|
28
28
|
import { isIdentityQuestion, identityAnswer } from './identity.js';
|
|
29
|
+
import { deniedCapabilities, capabilityCorrection, capabilityNotice } from './capability-claims.js';
|
|
29
30
|
import { leanTools, leanModeNotice, findToolsSchema, matchDeferred, deferredToolNotice, FIND_TOOLS } from './lean-mode.js';
|
|
30
31
|
import { findTailRepetition, trimTailRepetition, repetitionNotice, CHECK_EVERY } from './repetition.js';
|
|
31
32
|
import { loadMemory } from './memory.js';
|
|
@@ -1100,7 +1101,17 @@ export function toolDetailFor(name, argsJson) {
|
|
|
1100
1101
|
* before a conversation starts, so on the default there is almost no room to work in, and the
|
|
1101
1102
|
* symptom is exactly this: nothing comes back.
|
|
1102
1103
|
*/
|
|
1103
|
-
export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoint
|
|
1104
|
+
export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoint,
|
|
1105
|
+
/*
|
|
1106
|
+
* What this run's instructions and tool descriptions actually cost.
|
|
1107
|
+
*
|
|
1108
|
+
* This was the string "about 3.8k", with a comment four lines above it saying 3,600 — two
|
|
1109
|
+
* different figures for the same thing in the same file, neither of them measured. The real
|
|
1110
|
+
* number moves with the tools a run has: lean mode drops some, an MCP server adds more, and a
|
|
1111
|
+
* session with skills loaded is larger again. Quoting a constant is asserting something not
|
|
1112
|
+
* known, and it is the number somebody uses to decide what to set the window to.
|
|
1113
|
+
*/
|
|
1114
|
+
fixedTokens, platform) {
|
|
1104
1115
|
// The endpoint is named, not just the provider slot. Reported as "omni returned 3 empty
|
|
1105
1116
|
// responses ... yet i am using ollama" — both true at once, because the endpoint saved under the
|
|
1106
1117
|
// omni slot is an Ollama box. Naming the address answers the objection in the message itself.
|
|
@@ -1123,13 +1134,19 @@ export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoin
|
|
|
1123
1134
|
*/
|
|
1124
1135
|
const runtime = servesOwnWeights(provider);
|
|
1125
1136
|
const ownNetwork = endpoint ? servesLocally(provider, endpoint) : provider.toLowerCase() === 'ollama';
|
|
1137
|
+
const remote = endpoint ? !runsOnThisMachine(endpoint) : false;
|
|
1138
|
+
const cost = fixedTokens && fixedTokens > 0
|
|
1139
|
+
? `${fixedTokens.toLocaleString()} tokens of that — measured for this run, with the tools it `
|
|
1140
|
+
+ `actually has —`
|
|
1141
|
+
: 'most of that';
|
|
1126
1142
|
const ollamaFix = 'Ollama serves a default of 4,096 tokens whatever the model was trained for, and silently ' +
|
|
1127
1143
|
'truncates a longer prompt instead of refusing it — and KONECK\'s instructions and tool ' +
|
|
1128
|
-
|
|
1129
|
-
'how a turn comes back with nothing in it
|
|
1130
|
-
(endpoint
|
|
1131
|
-
|
|
1132
|
-
'limit, `/config contextTokens <n>` makes KONECK compact before
|
|
1144
|
+
`descriptions come to ${cost} before your conversation is counted, which is exactly ` +
|
|
1145
|
+
'how a turn comes back with nothing in it.\n\n' +
|
|
1146
|
+
ollamaWindowFix({ ...(endpoint !== undefined ? { endpoint } : {}), remote,
|
|
1147
|
+
...(platform !== undefined ? { platform } : {}) }) +
|
|
1148
|
+
'\n\nIf you know the real limit, `/config contextTokens <n>` makes KONECK compact before ' +
|
|
1149
|
+
'reaching it.';
|
|
1133
1150
|
if (runtime) {
|
|
1134
1151
|
return observed + '\nThe likeliest cause is the context window rather than the model. ' + ollamaFix;
|
|
1135
1152
|
}
|
|
@@ -1485,12 +1502,16 @@ detectedContext = { current: 0 }) {
|
|
|
1485
1502
|
let textToolsNoticed = false;
|
|
1486
1503
|
/** Said once: a model that writes calls as shell commands will keep doing it. */
|
|
1487
1504
|
let commandStyleNoticed = false;
|
|
1505
|
+
/** One correction per user message: a model that repeats a false denial will not be argued out. */
|
|
1506
|
+
let capabilityCorrected = false;
|
|
1488
1507
|
// The tool list, cut to fit when the window cannot hold it.
|
|
1489
1508
|
//
|
|
1490
1509
|
// Nine of the twenty-three tools are git subcommands, about 550 tokens between them, and every
|
|
1491
1510
|
// one is a line of shell that execute_command already runs. Reported as a session that planned
|
|
1492
1511
|
// three steps, called `plan`, and then returned three empty turns: there was nothing left of the
|
|
1493
1512
|
// window to answer in.
|
|
1513
|
+
/** What this run costs before the conversation is counted. Set just below, read at the plateau. */
|
|
1514
|
+
let fixedCost = 0;
|
|
1494
1515
|
// What every request costs before the user has typed anything — the instructions plus the tool
|
|
1495
1516
|
// descriptions. On a large window this is 12% of it and there is nothing to discuss. On a small
|
|
1496
1517
|
// one it was 93%, which is why a model looped, wrote tool calls as prose, and answered questions
|
|
@@ -1518,6 +1539,14 @@ detectedContext = { current: 0 }) {
|
|
|
1518
1539
|
fixed = fixedRequestTokens(systemChars, JSON.stringify(openaiTools));
|
|
1519
1540
|
}
|
|
1520
1541
|
}
|
|
1542
|
+
/*
|
|
1543
|
+
* Recorded after lean mode has had its go, so it is the floor that actually applies.
|
|
1544
|
+
*
|
|
1545
|
+
* A window discovered mid-stream is compared against this. Before lean mode it would overstate
|
|
1546
|
+
* the floor, and telling somebody their window cannot hold a prompt that has since been
|
|
1547
|
+
* trimmed to fit would be the wrong answer confidently given.
|
|
1548
|
+
*/
|
|
1549
|
+
fixedCost = fixed;
|
|
1521
1550
|
// Said only if it still does not fit after that, because otherwise it is solved rather than
|
|
1522
1551
|
// worth a warning.
|
|
1523
1552
|
if (overheadIsCrowded(fixed, effectiveLimit())) {
|
|
@@ -1527,9 +1556,8 @@ detectedContext = { current: 0 }) {
|
|
|
1527
1556
|
// OLLAMA_CONTEXT_LENGTH is read by the server, so for an endpoint on another machine it
|
|
1528
1557
|
// has to be set there. The advice said "in Ollama's environment", which for a remote box
|
|
1529
1558
|
// sends someone to restart the wrong one.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
: !/localhost|127\.0\.0\.1|::1/.test(resolveProvider(config.provider, config.baseURL).baseURL),
|
|
1559
|
+
// Loopback means this machine; a LAN address is somebody else's. See runsOnThisMachine.
|
|
1560
|
+
remote: !runsOnThisMachine(resolveProvider(config.provider, config.baseURL).baseURL),
|
|
1533
1561
|
});
|
|
1534
1562
|
config.onCrowdedWindow?.(fixed, effectiveLimit(), advice);
|
|
1535
1563
|
if (!silent)
|
|
@@ -1861,9 +1889,17 @@ detectedContext = { current: 0 }) {
|
|
|
1861
1889
|
truncationSeen = true;
|
|
1862
1890
|
contextLimit = plateau.window;
|
|
1863
1891
|
config.contextTokens = plateau.window; // so compaction plans around the real figure
|
|
1864
|
-
config.
|
|
1892
|
+
const endpoint = resolveProvider(config.provider, config.baseURL).baseURL;
|
|
1893
|
+
const said = truncationNotice(plateau.window, {
|
|
1894
|
+
floor: fixedCost,
|
|
1895
|
+
endpoint,
|
|
1896
|
+
// Whether the thing to reconfigure is this machine or another one. KONECK knows its
|
|
1897
|
+
// own platform and nothing about a remote box, so a remote answer names no commands.
|
|
1898
|
+
remote: !runsOnThisMachine(endpoint),
|
|
1899
|
+
});
|
|
1900
|
+
config.onTruncation?.(plateau.window, said);
|
|
1865
1901
|
if (!silent)
|
|
1866
|
-
tui.printWarning(
|
|
1902
|
+
tui.printWarning(said);
|
|
1867
1903
|
}
|
|
1868
1904
|
}
|
|
1869
1905
|
}
|
|
@@ -2341,6 +2377,36 @@ detectedContext = { current: 0 }) {
|
|
|
2341
2377
|
}
|
|
2342
2378
|
}
|
|
2343
2379
|
}
|
|
2380
|
+
/*
|
|
2381
|
+
* A reply that denies a capability this run has.
|
|
2382
|
+
*
|
|
2383
|
+
* "I cannot visit websites or perform login actions" — with the browser tool enabled, whose
|
|
2384
|
+
* description says it opens a url, fills form fields and clicks. The user's answer to that was
|
|
2385
|
+
* "but there's a built in browser right here", and they were right.
|
|
2386
|
+
*
|
|
2387
|
+
* Checked only after every recovery above has failed to find a call, because a model that wrote
|
|
2388
|
+
* a call as prose has not denied anything. And only for tools actually passed this turn: lean
|
|
2389
|
+
* mode defers tools and a machine may have no browser, and in those cases the denial is true.
|
|
2390
|
+
*
|
|
2391
|
+
* Once per user message. The correction goes to the model and the turn is taken again, because
|
|
2392
|
+
* being told the agent cannot help when it can is the worst outcome available — but a model that
|
|
2393
|
+
* says it a second time is not going to be argued into it, and the reader is better served by
|
|
2394
|
+
* the note than by a third attempt.
|
|
2395
|
+
*/
|
|
2396
|
+
if (toolCalls.length === 0 && assistantContent && !capabilityCorrected) {
|
|
2397
|
+
const denied = deniedCapabilities(assistantContent, openaiTools.map(t => t.function.name));
|
|
2398
|
+
if (denied.length > 0) {
|
|
2399
|
+
capabilityCorrected = true;
|
|
2400
|
+
const notice = capabilityNotice(denied, config.model);
|
|
2401
|
+
config.onFalseDenial?.(denied.map(d => d.what), notice);
|
|
2402
|
+
if (!silent)
|
|
2403
|
+
tui.printWarning(notice);
|
|
2404
|
+
messages.push({ role: 'assistant', content: assistantContent });
|
|
2405
|
+
messages.push({ role: 'system', content: capabilityCorrection(denied) });
|
|
2406
|
+
assistantContent = '';
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2344
2410
|
// Only print buffered content if it wasn't already live-streamed
|
|
2345
2411
|
if (!silent && assistantContent.trim() && !tui.hadStreamOutput())
|
|
2346
2412
|
tui.printAssistant(assistantContent);
|
|
@@ -2449,7 +2515,9 @@ detectedContext = { current: 0 }) {
|
|
|
2449
2515
|
// tools — and it said the model was not usable, about a model that had just called
|
|
2450
2516
|
// `plan` successfully one turn earlier. Neither helped, and both pointed away from the
|
|
2451
2517
|
// likely cause.
|
|
2452
|
-
throw new Error(emptyResponseAdvice(config.provider, config.model, emptyTurns, toolsProvenToWork, resolveProvider(config.provider, config.baseURL).baseURL
|
|
2518
|
+
throw new Error(emptyResponseAdvice(config.provider, config.model, emptyTurns, toolsProvenToWork, resolveProvider(config.provider, config.baseURL).baseURL,
|
|
2519
|
+
// The floor this run actually has, rather than a constant in a string.
|
|
2520
|
+
fixedCost));
|
|
2453
2521
|
}
|
|
2454
2522
|
}
|
|
2455
2523
|
else {
|