koneck 2.95.0 → 2.97.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 +24 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +155 -22
- package/dist/engine.js.map +1 -1
- package/dist/identity.d.ts +16 -4
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +20 -6
- package/dist/identity.js.map +1 -1
- package/dist/tools.d.ts +19 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +90 -17
- package/dist/tools.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';
|
|
@@ -658,7 +659,14 @@ export function truncateMemory(memory, limit = MAX_MEMORY_CHARS) {
|
|
|
658
659
|
const from = kept.indexOf('\n');
|
|
659
660
|
return '(earlier notes omitted)\n' + (from === -1 ? kept : kept.slice(from + 1));
|
|
660
661
|
}
|
|
661
|
-
|
|
662
|
+
/*
|
|
663
|
+
* Exported so what the model is told about its own identity can be asserted.
|
|
664
|
+
*
|
|
665
|
+
* That block is the whole reason identity.ts exists — a model told nothing about who wrote the
|
|
666
|
+
* program it is running reaches for whichever lab trained it — and it is a string in the middle of
|
|
667
|
+
* a large function, which is exactly the kind of thing that gets edited back to something wrong.
|
|
668
|
+
*/
|
|
669
|
+
export function buildSystemPrompt(ctx, memory = null, project = null, agentInstructions = null,
|
|
662
670
|
/**
|
|
663
671
|
* Whether the window is too small for guidance.
|
|
664
672
|
*
|
|
@@ -692,8 +700,10 @@ personality = 'default') {
|
|
|
692
700
|
const rules = `You are KONECK, a production-grade autonomous coding agent.
|
|
693
701
|
|
|
694
702
|
WHO YOU ARE — fact, not inference:
|
|
695
|
-
KONECK is
|
|
696
|
-
|
|
703
|
+
KONECK is an independent open-source coding agent. Who wrote it is deliberately not disclosed: you
|
|
704
|
+
do not know, and there is nothing to work out. If you are asked, say it is not disclosed. Never
|
|
705
|
+
name a person or a company as the author, and never guess — an omission is not an invitation to
|
|
706
|
+
fill the gap.
|
|
697
707
|
You are running on the model \`${ctx.model ?? 'unnamed'}\` served by \`${ctx.provider ?? 'unknown'}\`.
|
|
698
708
|
KONECK is the program; the model is a part it uses. You were NOT made by Anthropic, OpenAI,
|
|
699
709
|
Google, Alibaba or DeepSeek — the model's maker is not yours, and you must not claim to be the
|
|
@@ -876,6 +886,15 @@ export async function mapWithLimit(items, limit, fn) {
|
|
|
876
886
|
return results;
|
|
877
887
|
}
|
|
878
888
|
async function handleSpawnAgents(args, config, cwd,
|
|
889
|
+
/*
|
|
890
|
+
* The parent's stop, so sub-agents go down with it.
|
|
891
|
+
*
|
|
892
|
+
* They were started with no signal at all, so stopping a run that had fanned out to six agents
|
|
893
|
+
* stopped the parent and left six agents working — each with its own turns, its own tools and its
|
|
894
|
+
* own commands, none of which anybody could reach. That is the worst version of the stop problem
|
|
895
|
+
* rather than a corner of it: the visible run ends and the machine keeps going.
|
|
896
|
+
*/
|
|
897
|
+
abortSignal,
|
|
879
898
|
/**
|
|
880
899
|
* The parent's counters, so delegated work is counted as work.
|
|
881
900
|
*
|
|
@@ -919,6 +938,20 @@ delegated) {
|
|
|
919
938
|
// trip a per-second limit even when three concurrent is otherwise sustainable.
|
|
920
939
|
if (i > 0)
|
|
921
940
|
await new Promise(r => setTimeout(r, (i % concurrency) * AGENT_STAGGER_MS));
|
|
941
|
+
/*
|
|
942
|
+
* Nothing new is started after the stop.
|
|
943
|
+
*
|
|
944
|
+
* With a concurrency of three and nine tasks, the last six are queued behind the first three —
|
|
945
|
+
* so without this, stopping would be followed by six fresh agents starting up, each of which
|
|
946
|
+
* then has to be stopped in turn. A worktree is created here too, and one made for an agent
|
|
947
|
+
* that is not going to run is a directory somebody has to clean up.
|
|
948
|
+
*/
|
|
949
|
+
if (abortSignal?.aborted) {
|
|
950
|
+
mine.status = 'failed';
|
|
951
|
+
publish();
|
|
952
|
+
return { task, success: false,
|
|
953
|
+
output: 'NOT RUN: stopped at the user\'s request before this agent started.' };
|
|
954
|
+
}
|
|
922
955
|
let taskCwd = cwd;
|
|
923
956
|
let branch;
|
|
924
957
|
if (isGit && args.use_worktrees !== false) {
|
|
@@ -973,7 +1006,7 @@ delegated) {
|
|
|
973
1006
|
};
|
|
974
1007
|
try {
|
|
975
1008
|
const session = await createAgentSession(subConfig);
|
|
976
|
-
await session.send(task);
|
|
1009
|
+
await session.send(task, abortSignal);
|
|
977
1010
|
// The last assistant message with something in it — not merely the last, which after a
|
|
978
1011
|
// tool call is often an empty shell — and enough of it to be worth having. Three hundred
|
|
979
1012
|
// characters cut a sub-agent's conclusions mid-sentence, so the parent had to work out
|
|
@@ -1100,7 +1133,17 @@ export function toolDetailFor(name, argsJson) {
|
|
|
1100
1133
|
* before a conversation starts, so on the default there is almost no room to work in, and the
|
|
1101
1134
|
* symptom is exactly this: nothing comes back.
|
|
1102
1135
|
*/
|
|
1103
|
-
export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoint
|
|
1136
|
+
export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoint,
|
|
1137
|
+
/*
|
|
1138
|
+
* What this run's instructions and tool descriptions actually cost.
|
|
1139
|
+
*
|
|
1140
|
+
* This was the string "about 3.8k", with a comment four lines above it saying 3,600 — two
|
|
1141
|
+
* different figures for the same thing in the same file, neither of them measured. The real
|
|
1142
|
+
* number moves with the tools a run has: lean mode drops some, an MCP server adds more, and a
|
|
1143
|
+
* session with skills loaded is larger again. Quoting a constant is asserting something not
|
|
1144
|
+
* known, and it is the number somebody uses to decide what to set the window to.
|
|
1145
|
+
*/
|
|
1146
|
+
fixedTokens, platform) {
|
|
1104
1147
|
// The endpoint is named, not just the provider slot. Reported as "omni returned 3 empty
|
|
1105
1148
|
// responses ... yet i am using ollama" — both true at once, because the endpoint saved under the
|
|
1106
1149
|
// omni slot is an Ollama box. Naming the address answers the objection in the message itself.
|
|
@@ -1123,13 +1166,19 @@ export function emptyResponseAdvice(provider, model, count, toolsWorked, endpoin
|
|
|
1123
1166
|
*/
|
|
1124
1167
|
const runtime = servesOwnWeights(provider);
|
|
1125
1168
|
const ownNetwork = endpoint ? servesLocally(provider, endpoint) : provider.toLowerCase() === 'ollama';
|
|
1169
|
+
const remote = endpoint ? !runsOnThisMachine(endpoint) : false;
|
|
1170
|
+
const cost = fixedTokens && fixedTokens > 0
|
|
1171
|
+
? `${fixedTokens.toLocaleString()} tokens of that — measured for this run, with the tools it `
|
|
1172
|
+
+ `actually has —`
|
|
1173
|
+
: 'most of that';
|
|
1126
1174
|
const ollamaFix = 'Ollama serves a default of 4,096 tokens whatever the model was trained for, and silently ' +
|
|
1127
1175
|
'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
|
|
1176
|
+
`descriptions come to ${cost} before your conversation is counted, which is exactly ` +
|
|
1177
|
+
'how a turn comes back with nothing in it.\n\n' +
|
|
1178
|
+
ollamaWindowFix({ ...(endpoint !== undefined ? { endpoint } : {}), remote,
|
|
1179
|
+
...(platform !== undefined ? { platform } : {}) }) +
|
|
1180
|
+
'\n\nIf you know the real limit, `/config contextTokens <n>` makes KONECK compact before ' +
|
|
1181
|
+
'reaching it.';
|
|
1133
1182
|
if (runtime) {
|
|
1134
1183
|
return observed + '\nThe likeliest cause is the context window rather than the model. ' + ollamaFix;
|
|
1135
1184
|
}
|
|
@@ -1485,12 +1534,16 @@ detectedContext = { current: 0 }) {
|
|
|
1485
1534
|
let textToolsNoticed = false;
|
|
1486
1535
|
/** Said once: a model that writes calls as shell commands will keep doing it. */
|
|
1487
1536
|
let commandStyleNoticed = false;
|
|
1537
|
+
/** One correction per user message: a model that repeats a false denial will not be argued out. */
|
|
1538
|
+
let capabilityCorrected = false;
|
|
1488
1539
|
// The tool list, cut to fit when the window cannot hold it.
|
|
1489
1540
|
//
|
|
1490
1541
|
// Nine of the twenty-three tools are git subcommands, about 550 tokens between them, and every
|
|
1491
1542
|
// one is a line of shell that execute_command already runs. Reported as a session that planned
|
|
1492
1543
|
// three steps, called `plan`, and then returned three empty turns: there was nothing left of the
|
|
1493
1544
|
// window to answer in.
|
|
1545
|
+
/** What this run costs before the conversation is counted. Set just below, read at the plateau. */
|
|
1546
|
+
let fixedCost = 0;
|
|
1494
1547
|
// What every request costs before the user has typed anything — the instructions plus the tool
|
|
1495
1548
|
// descriptions. On a large window this is 12% of it and there is nothing to discuss. On a small
|
|
1496
1549
|
// one it was 93%, which is why a model looped, wrote tool calls as prose, and answered questions
|
|
@@ -1518,6 +1571,14 @@ detectedContext = { current: 0 }) {
|
|
|
1518
1571
|
fixed = fixedRequestTokens(systemChars, JSON.stringify(openaiTools));
|
|
1519
1572
|
}
|
|
1520
1573
|
}
|
|
1574
|
+
/*
|
|
1575
|
+
* Recorded after lean mode has had its go, so it is the floor that actually applies.
|
|
1576
|
+
*
|
|
1577
|
+
* A window discovered mid-stream is compared against this. Before lean mode it would overstate
|
|
1578
|
+
* the floor, and telling somebody their window cannot hold a prompt that has since been
|
|
1579
|
+
* trimmed to fit would be the wrong answer confidently given.
|
|
1580
|
+
*/
|
|
1581
|
+
fixedCost = fixed;
|
|
1521
1582
|
// Said only if it still does not fit after that, because otherwise it is solved rather than
|
|
1522
1583
|
// worth a warning.
|
|
1523
1584
|
if (overheadIsCrowded(fixed, effectiveLimit())) {
|
|
@@ -1527,9 +1588,8 @@ detectedContext = { current: 0 }) {
|
|
|
1527
1588
|
// OLLAMA_CONTEXT_LENGTH is read by the server, so for an endpoint on another machine it
|
|
1528
1589
|
// has to be set there. The advice said "in Ollama's environment", which for a remote box
|
|
1529
1590
|
// sends someone to restart the wrong one.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
: !/localhost|127\.0\.0\.1|::1/.test(resolveProvider(config.provider, config.baseURL).baseURL),
|
|
1591
|
+
// Loopback means this machine; a LAN address is somebody else's. See runsOnThisMachine.
|
|
1592
|
+
remote: !runsOnThisMachine(resolveProvider(config.provider, config.baseURL).baseURL),
|
|
1533
1593
|
});
|
|
1534
1594
|
config.onCrowdedWindow?.(fixed, effectiveLimit(), advice);
|
|
1535
1595
|
if (!silent)
|
|
@@ -1861,9 +1921,17 @@ detectedContext = { current: 0 }) {
|
|
|
1861
1921
|
truncationSeen = true;
|
|
1862
1922
|
contextLimit = plateau.window;
|
|
1863
1923
|
config.contextTokens = plateau.window; // so compaction plans around the real figure
|
|
1864
|
-
config.
|
|
1924
|
+
const endpoint = resolveProvider(config.provider, config.baseURL).baseURL;
|
|
1925
|
+
const said = truncationNotice(plateau.window, {
|
|
1926
|
+
floor: fixedCost,
|
|
1927
|
+
endpoint,
|
|
1928
|
+
// Whether the thing to reconfigure is this machine or another one. KONECK knows its
|
|
1929
|
+
// own platform and nothing about a remote box, so a remote answer names no commands.
|
|
1930
|
+
remote: !runsOnThisMachine(endpoint),
|
|
1931
|
+
});
|
|
1932
|
+
config.onTruncation?.(plateau.window, said);
|
|
1865
1933
|
if (!silent)
|
|
1866
|
-
tui.printWarning(
|
|
1934
|
+
tui.printWarning(said);
|
|
1867
1935
|
}
|
|
1868
1936
|
}
|
|
1869
1937
|
}
|
|
@@ -2341,6 +2409,36 @@ detectedContext = { current: 0 }) {
|
|
|
2341
2409
|
}
|
|
2342
2410
|
}
|
|
2343
2411
|
}
|
|
2412
|
+
/*
|
|
2413
|
+
* A reply that denies a capability this run has.
|
|
2414
|
+
*
|
|
2415
|
+
* "I cannot visit websites or perform login actions" — with the browser tool enabled, whose
|
|
2416
|
+
* description says it opens a url, fills form fields and clicks. The user's answer to that was
|
|
2417
|
+
* "but there's a built in browser right here", and they were right.
|
|
2418
|
+
*
|
|
2419
|
+
* Checked only after every recovery above has failed to find a call, because a model that wrote
|
|
2420
|
+
* a call as prose has not denied anything. And only for tools actually passed this turn: lean
|
|
2421
|
+
* mode defers tools and a machine may have no browser, and in those cases the denial is true.
|
|
2422
|
+
*
|
|
2423
|
+
* Once per user message. The correction goes to the model and the turn is taken again, because
|
|
2424
|
+
* being told the agent cannot help when it can is the worst outcome available — but a model that
|
|
2425
|
+
* says it a second time is not going to be argued into it, and the reader is better served by
|
|
2426
|
+
* the note than by a third attempt.
|
|
2427
|
+
*/
|
|
2428
|
+
if (toolCalls.length === 0 && assistantContent && !capabilityCorrected) {
|
|
2429
|
+
const denied = deniedCapabilities(assistantContent, openaiTools.map(t => t.function.name));
|
|
2430
|
+
if (denied.length > 0) {
|
|
2431
|
+
capabilityCorrected = true;
|
|
2432
|
+
const notice = capabilityNotice(denied, config.model);
|
|
2433
|
+
config.onFalseDenial?.(denied.map(d => d.what), notice);
|
|
2434
|
+
if (!silent)
|
|
2435
|
+
tui.printWarning(notice);
|
|
2436
|
+
messages.push({ role: 'assistant', content: assistantContent });
|
|
2437
|
+
messages.push({ role: 'system', content: capabilityCorrection(denied) });
|
|
2438
|
+
assistantContent = '';
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2344
2442
|
// Only print buffered content if it wasn't already live-streamed
|
|
2345
2443
|
if (!silent && assistantContent.trim() && !tui.hadStreamOutput())
|
|
2346
2444
|
tui.printAssistant(assistantContent);
|
|
@@ -2449,7 +2547,9 @@ detectedContext = { current: 0 }) {
|
|
|
2449
2547
|
// tools — and it said the model was not usable, about a model that had just called
|
|
2450
2548
|
// `plan` successfully one turn earlier. Neither helped, and both pointed away from the
|
|
2451
2549
|
// likely cause.
|
|
2452
|
-
throw new Error(emptyResponseAdvice(config.provider, config.model, emptyTurns, toolsProvenToWork, resolveProvider(config.provider, config.baseURL).baseURL
|
|
2550
|
+
throw new Error(emptyResponseAdvice(config.provider, config.model, emptyTurns, toolsProvenToWork, resolveProvider(config.provider, config.baseURL).baseURL,
|
|
2551
|
+
// The floor this run actually has, rather than a constant in a string.
|
|
2552
|
+
fixedCost));
|
|
2453
2553
|
}
|
|
2454
2554
|
}
|
|
2455
2555
|
else {
|
|
@@ -2571,7 +2671,36 @@ detectedContext = { current: 0 }) {
|
|
|
2571
2671
|
function: { name: tc.name, arguments: tc.arguments },
|
|
2572
2672
|
})),
|
|
2573
2673
|
});
|
|
2574
|
-
for (const tc of toolCalls) {
|
|
2674
|
+
for (const [callIndex, tc] of toolCalls.entries()) {
|
|
2675
|
+
/*
|
|
2676
|
+
* Stop means stop, including the calls already queued behind this one.
|
|
2677
|
+
*
|
|
2678
|
+
* A turn commonly arrives with several calls in it, and this loop ran every one of them
|
|
2679
|
+
* whatever the user had pressed — there was no check here at all. So stopping during a turn
|
|
2680
|
+
* that had asked for eight commands meant waiting for eight commands. Pressing it again did
|
|
2681
|
+
* nothing except abort an already-aborted signal, which is exactly the "I have to click many
|
|
2682
|
+
* times before it stops" that was reported: each press was honoured instantly and none of
|
|
2683
|
+
* them reached the thing actually taking the time.
|
|
2684
|
+
*
|
|
2685
|
+
* The ones not run still get an answer. Every tool_call in an assistant message must have a
|
|
2686
|
+
* matching tool result or the next request is rejected outright — so abandoning them silently
|
|
2687
|
+
* would trade a slow stop for a session that cannot be resumed, which is worse. Saying what
|
|
2688
|
+
* happened also keeps the transcript honest about which steps did not run.
|
|
2689
|
+
*/
|
|
2690
|
+
if (abortSignal?.aborted) {
|
|
2691
|
+
for (const skipped of toolCalls.slice(callIndex)) {
|
|
2692
|
+
messages.push({
|
|
2693
|
+
role: 'tool', tool_call_id: skipped.id,
|
|
2694
|
+
content: 'NOT RUN: stopped at the user\'s request before this call was made.',
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
if (!silent) {
|
|
2698
|
+
tui.printWarning(toolCalls.length - callIndex === 1
|
|
2699
|
+
? 'Stopped. One queued call was not run.'
|
|
2700
|
+
: `Stopped. ${toolCalls.length - callIndex} queued calls were not run.`);
|
|
2701
|
+
}
|
|
2702
|
+
return false;
|
|
2703
|
+
}
|
|
2575
2704
|
const sig = tc.name + '::' + tc.arguments;
|
|
2576
2705
|
/*
|
|
2577
2706
|
* Recorded on attempt, not on execution.
|
|
@@ -2886,14 +3015,16 @@ detectedContext = { current: 0 }) {
|
|
|
2886
3015
|
}
|
|
2887
3016
|
catch { /* use empty */ }
|
|
2888
3017
|
delegated.agents += parsed.tasks?.length ?? 0;
|
|
2889
|
-
result = await handleSpawnAgents(parsed, config, effectiveCwd, stats, delegated);
|
|
3018
|
+
result = await handleSpawnAgents(parsed, config, effectiveCwd, abortSignal, stats, delegated);
|
|
2890
3019
|
}
|
|
2891
3020
|
else if (BUILTIN_NAMES.has(tc.name)) {
|
|
2892
3021
|
result = await dispatchTool(tc.name, tc.arguments, effectiveCwd,
|
|
2893
3022
|
// Ink owns approvals through `config.approve`. Sending it through the legacy TUI as
|
|
2894
3023
|
// well created a second readline prompt over Ink's frame, which looked like a hang
|
|
2895
3024
|
// and could leave two terminal interfaces fighting for stdin.
|
|
2896
|
-
!config.approve && (config.requireApproval || policyDecision.requireApproval), line => config.onToolOutput?.(tc.name, line), sandbox
|
|
3025
|
+
!config.approve && (config.requireApproval || policyDecision.requireApproval), line => config.onToolOutput?.(tc.name, line), sandbox,
|
|
3026
|
+
// So a command, a page load or a fetch already in flight is cut rather than waited out.
|
|
3027
|
+
abortSignal);
|
|
2897
3028
|
}
|
|
2898
3029
|
else if (plugins.some(pl => pl.definition.name === tc.name)) {
|
|
2899
3030
|
result = await callPlugin(tc.name, tc.arguments, effectiveCwd, plugins);
|
|
@@ -2901,7 +3032,9 @@ detectedContext = { current: 0 }) {
|
|
|
2901
3032
|
else if (aliasedTool) {
|
|
2902
3033
|
// The name is not ours but its meaning is not in doubt: `create_file` with a path and
|
|
2903
3034
|
// contents is `write_file`. Refusing it wastes the turn and, twice now, the task.
|
|
2904
|
-
result = aliasNotice(tc.name, aliasedTool) + await dispatchTool(aliasedTool, tc.arguments, effectiveCwd, !config.approve && (config.requireApproval || policyDecision.requireApproval), line => config.onToolOutput?.(tc.name, line), sandbox
|
|
3035
|
+
result = aliasNotice(tc.name, aliasedTool) + await dispatchTool(aliasedTool, tc.arguments, effectiveCwd, !config.approve && (config.requireApproval || policyDecision.requireApproval), line => config.onToolOutput?.(tc.name, line), sandbox,
|
|
3036
|
+
// So a command, a page load or a fetch already in flight is cut rather than waited out.
|
|
3037
|
+
abortSignal);
|
|
2905
3038
|
}
|
|
2906
3039
|
else {
|
|
2907
3040
|
// A name the model invented, not a plugin that failed to load. Saying which tools exist
|