autopilot-ai 1.13.0 → 1.14.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/index.js +566 -180
- package/dist/index.js.map +1 -1
- package/dist/sdk/index.d.ts +20 -0
- package/dist/sdk/index.js +835 -153
- package/dist/sdk/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1832,9 +1832,14 @@ function shouldCompact(opts2) {
|
|
|
1832
1832
|
const { turns } = groupIntoTurns(opts2.messages);
|
|
1833
1833
|
return tokens > tokenThreshold || turns.length > turnThreshold;
|
|
1834
1834
|
}
|
|
1835
|
+
function isCompiledStateMessage(m) {
|
|
1836
|
+
return m.role === "system" && typeof m.content === "string" && m.content.startsWith("[compiled session state]");
|
|
1837
|
+
}
|
|
1835
1838
|
function compactMessagesViaArtifacts(opts2) {
|
|
1836
1839
|
const keepLastTurns = opts2.keepLastTurns ?? 4;
|
|
1837
|
-
const
|
|
1840
|
+
const grouped = groupIntoTurns(opts2.messages);
|
|
1841
|
+
const turns = grouped.turns;
|
|
1842
|
+
const prefix = grouped.prefix.filter((m) => !isCompiledStateMessage(m));
|
|
1838
1843
|
const tokensBefore = estimatePromptTokens(opts2.messages);
|
|
1839
1844
|
if (turns.length <= keepLastTurns) {
|
|
1840
1845
|
return {
|
|
@@ -4530,12 +4535,7 @@ ${opts2.lspContext}` : "";
|
|
|
4530
4535
|
The following MCP server information was pre-computed by the coordinator.
|
|
4531
4536
|
|
|
4532
4537
|
${opts2.mcpContext}` : "";
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
## Subagent policy for this turn
|
|
4536
|
-
|
|
4537
|
-
${opts2.delegationDirective}` : "";
|
|
4538
|
-
return identity + "\n\n" + env2 + "\n\n" + tools + lspBlock + userShellBlock + contextBlock + modeBlock + skillsBlock + memoryBlock + lspContextBlock + mcpContextBlock + delegationBlock;
|
|
4538
|
+
return identity + "\n\n" + env2 + "\n\n" + tools + lspBlock + userShellBlock + contextBlock + modeBlock + skillsBlock + memoryBlock + lspContextBlock + mcpContextBlock;
|
|
4539
4539
|
}
|
|
4540
4540
|
function buildSystemPrompt(opts2) {
|
|
4541
4541
|
return buildStaticPrefix(opts2) + "\n\n" + buildSessionPrefix(opts2);
|
|
@@ -4557,6 +4557,139 @@ var init_system_prompt = __esm({
|
|
|
4557
4557
|
}
|
|
4558
4558
|
});
|
|
4559
4559
|
|
|
4560
|
+
// src/agent/permission-gate.ts
|
|
4561
|
+
function createPermissionGate(ask, calls) {
|
|
4562
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
4563
|
+
let tail = Promise.resolve();
|
|
4564
|
+
return (req) => {
|
|
4565
|
+
const existing = decisions.get(req.sessionKey);
|
|
4566
|
+
if (existing) return existing;
|
|
4567
|
+
const sameTool = calls.filter((call) => call.name === req.tool.name);
|
|
4568
|
+
const request = sameTool.length > 1 ? { ...req, args: { ...req.args, batch: sameTool.map((call) => call.args) } } : req;
|
|
4569
|
+
const decision = tail.then(() => ask(request));
|
|
4570
|
+
tail = decision.catch(() => void 0);
|
|
4571
|
+
decisions.set(req.sessionKey, decision);
|
|
4572
|
+
return decision;
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
var init_permission_gate = __esm({
|
|
4576
|
+
"src/agent/permission-gate.ts"() {
|
|
4577
|
+
"use strict";
|
|
4578
|
+
}
|
|
4579
|
+
});
|
|
4580
|
+
|
|
4581
|
+
// src/agent/context-budget.ts
|
|
4582
|
+
function effectiveInputBudget(model, maxCompletionTokens) {
|
|
4583
|
+
const contextWindow = getModelOrInfer(model).contextWindow;
|
|
4584
|
+
const completion = maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
|
|
4585
|
+
return Math.max(1, contextWindow - completion - BUDGET_SAFETY_MARGIN_TOKENS);
|
|
4586
|
+
}
|
|
4587
|
+
function proactiveCompactionThreshold(model, maxCompletionTokens) {
|
|
4588
|
+
return Math.min(
|
|
4589
|
+
PROACTIVE_COMPACTION_CAP_TOKENS,
|
|
4590
|
+
Math.floor(effectiveInputBudget(model, maxCompletionTokens) * PROACTIVE_SHARE_OF_BUDGET)
|
|
4591
|
+
);
|
|
4592
|
+
}
|
|
4593
|
+
function compactToFit(messages, budgetTokens, target) {
|
|
4594
|
+
const tokensBefore = estimatePromptTokens(messages);
|
|
4595
|
+
for (const keepLastTurns of KEEP_TURNS_LADDER) {
|
|
4596
|
+
const scratch = new ArtifactStore();
|
|
4597
|
+
const attempt = compactMessagesViaArtifacts({ messages, state: target.getState(), store: scratch, keepLastTurns });
|
|
4598
|
+
if (attempt.metrics.rawTurnsRemoved === 0) continue;
|
|
4599
|
+
const isLast = keepLastTurns === KEEP_TURNS_LADDER[KEEP_TURNS_LADDER.length - 1];
|
|
4600
|
+
if (attempt.metrics.estimatedTokensAfter > budgetTokens && !isLast) continue;
|
|
4601
|
+
const store = target.getStore();
|
|
4602
|
+
for (const artifact of scratch.list()) store.add(artifact);
|
|
4603
|
+
target.setState(attempt.newState);
|
|
4604
|
+
target.onChange?.();
|
|
4605
|
+
return {
|
|
4606
|
+
messages: attempt.newMessages,
|
|
4607
|
+
tokensBefore,
|
|
4608
|
+
tokensAfter: attempt.metrics.estimatedTokensAfter,
|
|
4609
|
+
turnsRemoved: attempt.metrics.rawTurnsRemoved,
|
|
4610
|
+
artifactsArchived: attempt.metrics.archivedArtifacts
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
return null;
|
|
4614
|
+
}
|
|
4615
|
+
function sessionFileCompactionTarget(file) {
|
|
4616
|
+
let store = null;
|
|
4617
|
+
return {
|
|
4618
|
+
getState: () => file.sessionState ?? emptySessionState(),
|
|
4619
|
+
setState: (state) => {
|
|
4620
|
+
file.sessionState = state;
|
|
4621
|
+
},
|
|
4622
|
+
getStore: () => store ??= file.artifactStore ? deserializeArtifactStore(file.artifactStore) : new ArtifactStore(),
|
|
4623
|
+
onChange: () => {
|
|
4624
|
+
if (store) file.artifactStore = serializeArtifactStore(store);
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
var DEFAULT_MAX_COMPLETION_TOKENS, BUDGET_SAFETY_MARGIN_TOKENS, PROACTIVE_COMPACTION_CAP_TOKENS, PROACTIVE_SHARE_OF_BUDGET, KEEP_TURNS_LADDER, ContextBudgetError;
|
|
4629
|
+
var init_context_budget = __esm({
|
|
4630
|
+
"src/agent/context-budget.ts"() {
|
|
4631
|
+
"use strict";
|
|
4632
|
+
init_registry();
|
|
4633
|
+
init_artifact_compaction();
|
|
4634
|
+
init_session_state();
|
|
4635
|
+
DEFAULT_MAX_COMPLETION_TOKENS = 16384;
|
|
4636
|
+
BUDGET_SAFETY_MARGIN_TOKENS = 8192;
|
|
4637
|
+
PROACTIVE_COMPACTION_CAP_TOKENS = 8e4;
|
|
4638
|
+
PROACTIVE_SHARE_OF_BUDGET = 0.75;
|
|
4639
|
+
KEEP_TURNS_LADDER = [4, 2, 1];
|
|
4640
|
+
ContextBudgetError = class extends Error {
|
|
4641
|
+
constructor(estimatedTokens, budgetTokens, contextWindow) {
|
|
4642
|
+
super(
|
|
4643
|
+
`kimiflare: context window exceeded (~${estimatedTokens.toLocaleString()} tokens needed, ${budgetTokens.toLocaleString()} available of ${contextWindow.toLocaleString()}). Older turns were already compacted; the current turn alone is too large. Run /clear to start fresh, pick a model with a larger context window, or reduce the size of the request (e.g. fewer or smaller attachments).`
|
|
4644
|
+
);
|
|
4645
|
+
this.estimatedTokens = estimatedTokens;
|
|
4646
|
+
this.budgetTokens = budgetTokens;
|
|
4647
|
+
this.contextWindow = contextWindow;
|
|
4648
|
+
this.name = "ContextBudgetError";
|
|
4649
|
+
}
|
|
4650
|
+
estimatedTokens;
|
|
4651
|
+
budgetTokens;
|
|
4652
|
+
contextWindow;
|
|
4653
|
+
};
|
|
4654
|
+
}
|
|
4655
|
+
});
|
|
4656
|
+
|
|
4657
|
+
// src/agent/turn-directive.ts
|
|
4658
|
+
function withTurnDirective(messages, directive, turnUserMessage) {
|
|
4659
|
+
let out = messages;
|
|
4660
|
+
if (messages.some(hasPersistedBlock)) {
|
|
4661
|
+
out = messages.map(
|
|
4662
|
+
(m) => hasPersistedBlock(m) ? { ...m, content: m.content.replace(PERSISTED_BLOCK, "") } : m
|
|
4663
|
+
);
|
|
4664
|
+
}
|
|
4665
|
+
if (!directive) return out;
|
|
4666
|
+
let anchor = turnUserMessage ? out.indexOf(turnUserMessage) : -1;
|
|
4667
|
+
if (anchor === -1) {
|
|
4668
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
4669
|
+
if (out[i].role === "user") {
|
|
4670
|
+
anchor = i;
|
|
4671
|
+
break;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
const directiveMessage = { role: "system", content: `${HEADING}
|
|
4676
|
+
|
|
4677
|
+
${directive}` };
|
|
4678
|
+
const insertAt = anchor === -1 ? out.length : anchor + 1;
|
|
4679
|
+
return [...out.slice(0, insertAt), directiveMessage, ...out.slice(insertAt)];
|
|
4680
|
+
}
|
|
4681
|
+
function hasPersistedBlock(m) {
|
|
4682
|
+
return m.role === "system" && typeof m.content === "string" && m.content.includes(HEADING);
|
|
4683
|
+
}
|
|
4684
|
+
var HEADING, PERSISTED_BLOCK;
|
|
4685
|
+
var init_turn_directive = __esm({
|
|
4686
|
+
"src/agent/turn-directive.ts"() {
|
|
4687
|
+
"use strict";
|
|
4688
|
+
HEADING = "## Subagent policy for this turn";
|
|
4689
|
+
PERSISTED_BLOCK = /\n*## Subagent policy for this turn\n[\s\S]*?(?=\n## |$)/;
|
|
4690
|
+
}
|
|
4691
|
+
});
|
|
4692
|
+
|
|
4560
4693
|
// src/agent/loop.ts
|
|
4561
4694
|
function getSessionWebFetchHistory(sessionId) {
|
|
4562
4695
|
const key = sessionId ?? "default";
|
|
@@ -4621,6 +4754,7 @@ async function runAgentTurn(opts2) {
|
|
|
4621
4754
|
let memoryRecalledCount = 0;
|
|
4622
4755
|
let skillResult;
|
|
4623
4756
|
const lastUserPrompt = extractLastUserText(opts2.messages);
|
|
4757
|
+
const turnUserMessage = [...opts2.messages].reverse().find((m) => m.role === "user");
|
|
4624
4758
|
const userPromptPreview = lastUserPrompt.slice(0, 200);
|
|
4625
4759
|
const skipSkillRouting = opts2.intentClassification?.tier === "light" && lastUserPrompt.length < 40;
|
|
4626
4760
|
const recallPromise = opts2.sessionStartRecall && opts2.memoryManager && !hasRecalledMemory(opts2.messages) ? (async () => {
|
|
@@ -4670,8 +4804,7 @@ async function runAgentTurn(opts2) {
|
|
|
4670
4804
|
model: opts2.model,
|
|
4671
4805
|
mode: opts2.mode,
|
|
4672
4806
|
skillContext: skillResult.skillContext,
|
|
4673
|
-
preferPullRequests: opts2.preferPullRequests
|
|
4674
|
-
delegationDirective: opts2.delegationDirective
|
|
4807
|
+
preferPullRequests: opts2.preferPullRequests
|
|
4675
4808
|
})
|
|
4676
4809
|
};
|
|
4677
4810
|
} else {
|
|
@@ -4683,8 +4816,7 @@ async function runAgentTurn(opts2) {
|
|
|
4683
4816
|
model: opts2.model,
|
|
4684
4817
|
mode: opts2.mode,
|
|
4685
4818
|
skillContext: skillResult.skillContext,
|
|
4686
|
-
preferPullRequests: opts2.preferPullRequests
|
|
4687
|
-
delegationDirective: opts2.delegationDirective
|
|
4819
|
+
preferPullRequests: opts2.preferPullRequests
|
|
4688
4820
|
})
|
|
4689
4821
|
};
|
|
4690
4822
|
}
|
|
@@ -4752,7 +4884,6 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
4752
4884
|
let webFetchesThisTurn = 0;
|
|
4753
4885
|
let codeModeRedirects = 0;
|
|
4754
4886
|
const MAX_WEB_FETCH_PER_TURN = 5;
|
|
4755
|
-
const WEB_FETCH_DOMAIN_THRESHOLD = 2;
|
|
4756
4887
|
let cumulativePromptTokens = 0;
|
|
4757
4888
|
let iter = 0;
|
|
4758
4889
|
let totalIter = 0;
|
|
@@ -4770,6 +4901,11 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
4770
4901
|
mutatingToolsSinceLastTasksSet = 0;
|
|
4771
4902
|
originalOnTasks?.(tasks);
|
|
4772
4903
|
};
|
|
4904
|
+
const appendPendingInput = () => {
|
|
4905
|
+
const pending = opts2.drainPendingInput?.() ?? [];
|
|
4906
|
+
for (const msg of pending) opts2.messages.push(msg);
|
|
4907
|
+
return pending.length;
|
|
4908
|
+
};
|
|
4773
4909
|
while (true) {
|
|
4774
4910
|
if (budgetExhausted) {
|
|
4775
4911
|
opts2.messages.push({
|
|
@@ -4827,46 +4963,53 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
4827
4963
|
const shadowStrip = process.env.KIMIFLARE_SHADOW_STRIP === "1";
|
|
4828
4964
|
const keepLastRaw = process.env.KIMIFLARE_REASONING_KEEP_LAST;
|
|
4829
4965
|
const keepLast = keepLastRaw ? parseInt(keepLastRaw, 10) : 1;
|
|
4830
|
-
let apiMessages = opts2.messages;
|
|
4831
4966
|
let shadowStripMetrics;
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
(sum,
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
originalApproxTokens,
|
|
4849
|
-
strippedApproxTokens,
|
|
4850
|
-
savingsPct: originalApproxTokens > 0 ? Math.round(
|
|
4851
|
-
(originalApproxTokens - strippedApproxTokens) / originalApproxTokens * 100
|
|
4852
|
-
) : 0
|
|
4853
|
-
};
|
|
4967
|
+
const buildApiMessages = () => {
|
|
4968
|
+
let out = opts2.messages;
|
|
4969
|
+
if (stripReasoning || shadowStrip) {
|
|
4970
|
+
const stripped = stripHistoricalReasoning(opts2.messages, {
|
|
4971
|
+
keepLast: Number.isNaN(keepLast) ? 1 : keepLast
|
|
4972
|
+
});
|
|
4973
|
+
if (shadowStrip) {
|
|
4974
|
+
const originalApproxTokens = analyzePrompt(opts2.messages).reduce((sum, sec) => sum + sec.approxTokens, 0);
|
|
4975
|
+
const strippedApproxTokens = analyzePrompt(stripped).reduce((sum, sec) => sum + sec.approxTokens, 0);
|
|
4976
|
+
shadowStripMetrics = {
|
|
4977
|
+
originalApproxTokens,
|
|
4978
|
+
strippedApproxTokens,
|
|
4979
|
+
savingsPct: originalApproxTokens > 0 ? Math.round((originalApproxTokens - strippedApproxTokens) / originalApproxTokens * 100) : 0
|
|
4980
|
+
};
|
|
4981
|
+
}
|
|
4982
|
+
if (stripReasoning) out = stripped;
|
|
4854
4983
|
}
|
|
4855
|
-
if (
|
|
4856
|
-
|
|
4984
|
+
if (opts2.keepLastImageTurns !== void 0) {
|
|
4985
|
+
out = stripOldImages(out, opts2.keepLastImageTurns);
|
|
4986
|
+
}
|
|
4987
|
+
return withTurnDirective(out, opts2.delegationDirective, turnUserMessage);
|
|
4988
|
+
};
|
|
4989
|
+
let apiMessages = buildApiMessages();
|
|
4990
|
+
let promptTokens = estimatePromptTokens(apiMessages);
|
|
4991
|
+
const maxPromptTokens = effectiveInputBudget(opts2.model, opts2.maxCompletionTokens);
|
|
4992
|
+
if (promptTokens > maxPromptTokens && opts2.compaction) {
|
|
4993
|
+
const requestOverhead = Math.max(0, promptTokens - estimatePromptTokens(opts2.messages));
|
|
4994
|
+
const outcome = compactToFit(opts2.messages, maxPromptTokens - requestOverhead, opts2.compaction);
|
|
4995
|
+
if (outcome) {
|
|
4996
|
+
opts2.messages.splice(0, opts2.messages.length, ...outcome.messages);
|
|
4997
|
+
apiMessages = buildApiMessages();
|
|
4998
|
+
promptTokens = estimatePromptTokens(apiMessages);
|
|
4999
|
+
const info = {
|
|
5000
|
+
tokensBefore: outcome.tokensBefore,
|
|
5001
|
+
tokensAfter: outcome.tokensAfter,
|
|
5002
|
+
turnsRemoved: outcome.turnsRemoved,
|
|
5003
|
+
artifactsArchived: outcome.artifactsArchived,
|
|
5004
|
+
budgetTokens: maxPromptTokens
|
|
5005
|
+
};
|
|
5006
|
+
logger.info("context:compacted", { sessionId: opts2.sessionId, ...info });
|
|
5007
|
+
opts2.callbacks.onCompacted?.(info);
|
|
4857
5008
|
}
|
|
4858
5009
|
}
|
|
4859
|
-
if (opts2.keepLastImageTurns !== void 0) {
|
|
4860
|
-
apiMessages = stripOldImages(apiMessages, opts2.keepLastImageTurns);
|
|
4861
|
-
}
|
|
4862
|
-
const promptTokens = estimatePromptTokens(apiMessages);
|
|
4863
|
-
const ctxWindow = getModelOrInfer(opts2.model).contextWindow;
|
|
4864
|
-
const completionBudget = opts2.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
|
|
4865
|
-
const maxPromptTokens = ctxWindow - completionBudget - BUDGET_SAFETY_MARGIN_TOKENS;
|
|
4866
5010
|
if (promptTokens > maxPromptTokens) {
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
);
|
|
5011
|
+
logger.warn("context:irreducible", { sessionId: opts2.sessionId, promptTokens, budgetTokens: maxPromptTokens });
|
|
5012
|
+
throw new ContextBudgetError(promptTokens, maxPromptTokens, getModelOrInfer(opts2.model).contextWindow);
|
|
4870
5013
|
}
|
|
4871
5014
|
logger.debug("turn:api_request", { sessionId: opts2.sessionId, messageCount: apiMessages.length });
|
|
4872
5015
|
const events = runKimi({
|
|
@@ -4989,6 +5132,7 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
4989
5132
|
if (budgetExhausted) {
|
|
4990
5133
|
throw new BudgetExhaustedError();
|
|
4991
5134
|
}
|
|
5135
|
+
if (appendPendingInput() > 0) continue;
|
|
4992
5136
|
logger.info("turn:complete", { sessionId: opts2.sessionId, durationMs: Math.round(performance.now() - turnStart) });
|
|
4993
5137
|
await fireStopHook();
|
|
4994
5138
|
return;
|
|
@@ -4996,12 +5140,20 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
4996
5140
|
let blockedCount = 0;
|
|
4997
5141
|
const availableToolNames = new Set(opts2.tools.map((tool) => tool.name));
|
|
4998
5142
|
if (codeMode) availableToolNames.add("execute_code");
|
|
4999
|
-
const
|
|
5000
|
-
|
|
5001
|
-
|
|
5143
|
+
const executorTools = opts2.executor.list();
|
|
5144
|
+
const allConcurrent = toolCalls.length > 1 && toolCalls.every((tc) => {
|
|
5145
|
+
const tool = executorTools.find((t) => t.name === tc.function.name);
|
|
5146
|
+
return availableToolNames.has(tc.function.name) && (tool?.isReadOnly === true || tool?.concurrent === true);
|
|
5002
5147
|
});
|
|
5003
|
-
if (
|
|
5004
|
-
|
|
5148
|
+
if (allConcurrent) {
|
|
5149
|
+
const workerCount = toolCalls.filter((tc) => executorTools.find((t) => t.name === tc.function.name)?.concurrent === true).length;
|
|
5150
|
+
opts2.callbacks.onInfo?.(
|
|
5151
|
+
workerCount > 0 ? `${toolCalls.length} tool calls running in parallel (${workerCount} isolated worker${workerCount === 1 ? "" : "s"})` : `${toolCalls.length} read-only tools running in parallel`
|
|
5152
|
+
);
|
|
5153
|
+
const batchAskPermission = createPermissionGate(
|
|
5154
|
+
opts2.callbacks.askPermission,
|
|
5155
|
+
toolCalls.map((tc) => ({ name: tc.function.name, args: parseArgsObject(tc.function.arguments) }))
|
|
5156
|
+
);
|
|
5005
5157
|
const items = [];
|
|
5006
5158
|
for (const tc of toolCalls) {
|
|
5007
5159
|
if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
|
|
@@ -5016,7 +5168,8 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5016
5168
|
tool_call_id: tc.id,
|
|
5017
5169
|
name: tc.function.name,
|
|
5018
5170
|
content: `Loop detected: you have called ${tc.function.name} with the same arguments multiple times in a row. Consider a different approach.`,
|
|
5019
|
-
ok: false
|
|
5171
|
+
ok: false,
|
|
5172
|
+
guardrail: true
|
|
5020
5173
|
}
|
|
5021
5174
|
});
|
|
5022
5175
|
continue;
|
|
@@ -5035,27 +5188,19 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5035
5188
|
const args = JSON.parse(tc.function.arguments || "{}");
|
|
5036
5189
|
const url = args.url || "";
|
|
5037
5190
|
try {
|
|
5038
|
-
|
|
5039
|
-
const domainCount = webFetchHistory.filter((h) => h.domain === domain).length;
|
|
5191
|
+
new URL(url);
|
|
5040
5192
|
const totalSessionFetches = webFetchHistory.length;
|
|
5041
|
-
if (webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN || totalSessionFetches >= SESSION_WEB_FETCH_CAP
|
|
5042
|
-
|
|
5043
|
-
if (webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN) {
|
|
5044
|
-
warning = `Research budget exceeded: you have already made ${MAX_WEB_FETCH_PER_TURN} web requests this turn. Synthesize what you have learned instead of fetching more pages.`;
|
|
5045
|
-
} else if (totalSessionFetches >= SESSION_WEB_FETCH_CAP) {
|
|
5046
|
-
warning = `Session research budget exceeded: ${totalSessionFetches} web fetches across this session. Synthesize what you have learned from prior fetches instead of starting another page.`;
|
|
5047
|
-
} else {
|
|
5048
|
-
warning = `Loop detected: you have fetched from ${domain} multiple times. Consider a different approach or synthesize existing findings.`;
|
|
5049
|
-
}
|
|
5193
|
+
if (webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN || totalSessionFetches >= SESSION_WEB_FETCH_CAP) {
|
|
5194
|
+
const warning = webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN ? `Research budget exceeded: you have already made ${MAX_WEB_FETCH_PER_TURN} web requests this turn. Synthesize what you have learned instead of fetching more pages.` : `Session research budget exceeded: ${totalSessionFetches} web fetches across this session. Synthesize what you have learned from prior fetches instead of starting another page.`;
|
|
5050
5195
|
items.push({
|
|
5051
5196
|
kind: "blocked",
|
|
5052
5197
|
tc,
|
|
5053
5198
|
loopSignature,
|
|
5054
|
-
result: { tool_call_id: tc.id, name: "web_fetch", content: warning, ok: false }
|
|
5199
|
+
result: { tool_call_id: tc.id, name: "web_fetch", content: warning, ok: false, guardrail: true }
|
|
5055
5200
|
});
|
|
5056
5201
|
continue;
|
|
5057
5202
|
}
|
|
5058
|
-
webFetchHistory.push(
|
|
5203
|
+
webFetchHistory.push(url);
|
|
5059
5204
|
webFetchesThisTurn++;
|
|
5060
5205
|
} catch {
|
|
5061
5206
|
}
|
|
@@ -5069,7 +5214,7 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5069
5214
|
logger.debug("turn:tool_start", { sessionId: opts2.sessionId, tool: it.tc.function.name, toolCallId: it.tc.id });
|
|
5070
5215
|
const result2 = await opts2.executor.run(
|
|
5071
5216
|
{ id: it.tc.id, name: it.tc.function.name, arguments: it.tc.function.arguments },
|
|
5072
|
-
|
|
5217
|
+
batchAskPermission,
|
|
5073
5218
|
{
|
|
5074
5219
|
cwd: opts2.cwd,
|
|
5075
5220
|
signal: opts2.signal,
|
|
@@ -5085,6 +5230,7 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5085
5230
|
model: opts2.model,
|
|
5086
5231
|
allowDirectPush: opts2.allowDirectPush,
|
|
5087
5232
|
runId: opts2.runId,
|
|
5233
|
+
onRunBudgetExceeded: opts2.callbacks.onRunBudgetExceeded,
|
|
5088
5234
|
runsDbPath: opts2.runsDbPath
|
|
5089
5235
|
},
|
|
5090
5236
|
opts2.onFileChange
|
|
@@ -5267,7 +5413,8 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5267
5413
|
tool_call_id: tc.id,
|
|
5268
5414
|
name: tc.function.name,
|
|
5269
5415
|
content: warning,
|
|
5270
|
-
ok: false
|
|
5416
|
+
ok: false,
|
|
5417
|
+
guardrail: true
|
|
5271
5418
|
};
|
|
5272
5419
|
toolResults.push(loopResult);
|
|
5273
5420
|
opts2.messages.push({
|
|
@@ -5286,8 +5433,7 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5286
5433
|
const args = JSON.parse(tc.function.arguments || "{}");
|
|
5287
5434
|
const url = args.url || "";
|
|
5288
5435
|
try {
|
|
5289
|
-
|
|
5290
|
-
const domainCount = webFetchHistory.filter((h) => h.domain === domain).length;
|
|
5436
|
+
new URL(url);
|
|
5291
5437
|
const totalSessionFetches = webFetchHistory.length;
|
|
5292
5438
|
if (webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN) {
|
|
5293
5439
|
const warning = `Research budget exceeded: you have already made ${MAX_WEB_FETCH_PER_TURN} web requests this turn. Synthesize what you have learned instead of fetching more pages.`;
|
|
@@ -5295,7 +5441,8 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5295
5441
|
tool_call_id: tc.id,
|
|
5296
5442
|
name: "web_fetch",
|
|
5297
5443
|
content: warning,
|
|
5298
|
-
ok: false
|
|
5444
|
+
ok: false,
|
|
5445
|
+
guardrail: true
|
|
5299
5446
|
};
|
|
5300
5447
|
toolResults.push(budgetResult);
|
|
5301
5448
|
opts2.messages.push({
|
|
@@ -5316,7 +5463,8 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5316
5463
|
tool_call_id: tc.id,
|
|
5317
5464
|
name: "web_fetch",
|
|
5318
5465
|
content: warning,
|
|
5319
|
-
ok: false
|
|
5466
|
+
ok: false,
|
|
5467
|
+
guardrail: true
|
|
5320
5468
|
};
|
|
5321
5469
|
toolResults.push(sessionCapResult);
|
|
5322
5470
|
opts2.messages.push({
|
|
@@ -5331,28 +5479,7 @@ IMPORTANT \u2014 explore through code, not direct tools: to read files, run shel
|
|
|
5331
5479
|
blockedCount++;
|
|
5332
5480
|
continue;
|
|
5333
5481
|
}
|
|
5334
|
-
|
|
5335
|
-
const warning = `Loop detected: you have fetched from ${domain} multiple times. Consider a different approach or synthesize existing findings.`;
|
|
5336
|
-
const loopResult = {
|
|
5337
|
-
tool_call_id: tc.id,
|
|
5338
|
-
name: "web_fetch",
|
|
5339
|
-
content: warning,
|
|
5340
|
-
ok: false
|
|
5341
|
-
};
|
|
5342
|
-
toolResults.push(loopResult);
|
|
5343
|
-
opts2.messages.push({
|
|
5344
|
-
role: "tool",
|
|
5345
|
-
tool_call_id: tc.id,
|
|
5346
|
-
content: sanitizeString(warning),
|
|
5347
|
-
name: "web_fetch"
|
|
5348
|
-
});
|
|
5349
|
-
opts2.callbacks.onToolResult?.(loopResult);
|
|
5350
|
-
recentToolCalls.push(loopSignature);
|
|
5351
|
-
if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
|
|
5352
|
-
blockedCount++;
|
|
5353
|
-
continue;
|
|
5354
|
-
}
|
|
5355
|
-
webFetchHistory.push({ url, domain });
|
|
5482
|
+
webFetchHistory.push(url);
|
|
5356
5483
|
webFetchesThisTurn++;
|
|
5357
5484
|
} catch {
|
|
5358
5485
|
}
|
|
@@ -5490,7 +5617,7 @@ ${sandboxResult.output}` : sandboxResult.output;
|
|
|
5490
5617
|
opts2.callbacks.onToolResult?.(result2);
|
|
5491
5618
|
if (result2.waitRequest) {
|
|
5492
5619
|
if (opts2.onIterationEnd) {
|
|
5493
|
-
opts2.messages
|
|
5620
|
+
replaceMessagesInPlace(opts2.messages, await opts2.onIterationEnd(opts2.messages, opts2.signal));
|
|
5494
5621
|
}
|
|
5495
5622
|
logger.info("run:yielded", {
|
|
5496
5623
|
runId: result2.waitRequest.runId,
|
|
@@ -5621,8 +5748,9 @@ ${sandboxResult.output}` : sandboxResult.output;
|
|
|
5621
5748
|
if (blockedCount === toolCalls.length && toolCalls.length > 0) {
|
|
5622
5749
|
loopExhausted = true;
|
|
5623
5750
|
}
|
|
5751
|
+
appendPendingInput();
|
|
5624
5752
|
if (opts2.onIterationEnd) {
|
|
5625
|
-
opts2.messages
|
|
5753
|
+
replaceMessagesInPlace(opts2.messages, await opts2.onIterationEnd(opts2.messages, opts2.signal));
|
|
5626
5754
|
if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
|
|
5627
5755
|
}
|
|
5628
5756
|
if (opts2.sessionId && lastUsage) {
|
|
@@ -5674,6 +5802,18 @@ ${sandboxResult.output}` : sandboxResult.output;
|
|
|
5674
5802
|
}
|
|
5675
5803
|
}
|
|
5676
5804
|
}
|
|
5805
|
+
function parseArgsObject(raw) {
|
|
5806
|
+
try {
|
|
5807
|
+
const parsed = JSON.parse(raw || "{}");
|
|
5808
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
5809
|
+
} catch {
|
|
5810
|
+
return {};
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
function replaceMessagesInPlace(target, next) {
|
|
5814
|
+
if (next === target) return;
|
|
5815
|
+
target.splice(0, target.length, ...next);
|
|
5816
|
+
}
|
|
5677
5817
|
function validateToolArguments(raw) {
|
|
5678
5818
|
if (!raw || !raw.trim()) return "{}";
|
|
5679
5819
|
try {
|
|
@@ -5691,7 +5831,7 @@ function llmAuthOf(opts2) {
|
|
|
5691
5831
|
...opts2.provider ? { provider: opts2.provider } : {}
|
|
5692
5832
|
};
|
|
5693
5833
|
}
|
|
5694
|
-
var MAX_LOOP_RECOVERIES, BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftEvents, DRIFT_WINDOW, DRIFT_THRESHOLD, memoryExtractionErrorCounts, sessionWebFetchHistory, SESSION_WEB_FETCH_CAP,
|
|
5834
|
+
var MAX_LOOP_RECOVERIES, BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftEvents, DRIFT_WINDOW, DRIFT_THRESHOLD, memoryExtractionErrorCounts, sessionWebFetchHistory, SESSION_WEB_FETCH_CAP, MAX_TOOL_CONTENT_CHARS, CODE_MODE_REDIRECT_TOOLS, MAX_CODE_MODE_REDIRECTS;
|
|
5695
5835
|
var init_loop = __esm({
|
|
5696
5836
|
"src/agent/loop.ts"() {
|
|
5697
5837
|
"use strict";
|
|
@@ -5708,6 +5848,9 @@ var init_loop = __esm({
|
|
|
5708
5848
|
init_router();
|
|
5709
5849
|
init_system_prompt();
|
|
5710
5850
|
init_registry();
|
|
5851
|
+
init_permission_gate();
|
|
5852
|
+
init_context_budget();
|
|
5853
|
+
init_turn_directive();
|
|
5711
5854
|
MAX_LOOP_RECOVERIES = 1;
|
|
5712
5855
|
BudgetExhaustedError = class extends Error {
|
|
5713
5856
|
constructor(message2 = "Cumulative input token budget exhausted") {
|
|
@@ -5728,8 +5871,6 @@ var init_loop = __esm({
|
|
|
5728
5871
|
memoryExtractionErrorCounts = /* @__PURE__ */ new Map();
|
|
5729
5872
|
sessionWebFetchHistory = /* @__PURE__ */ new Map();
|
|
5730
5873
|
SESSION_WEB_FETCH_CAP = 25;
|
|
5731
|
-
DEFAULT_MAX_COMPLETION_TOKENS = 16384;
|
|
5732
|
-
BUDGET_SAFETY_MARGIN_TOKENS = 8192;
|
|
5733
5874
|
MAX_TOOL_CONTENT_CHARS = 1e4;
|
|
5734
5875
|
CODE_MODE_REDIRECT_TOOLS = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob"]);
|
|
5735
5876
|
MAX_CODE_MODE_REDIRECTS = 4;
|
|
@@ -6664,47 +6805,64 @@ function parsePushTarget(command) {
|
|
|
6664
6805
|
}
|
|
6665
6806
|
return { kind: "ref", ref: last.replace(/^refs\/heads\//, "") };
|
|
6666
6807
|
}
|
|
6808
|
+
function buildCoauthorTrailer(coauthor) {
|
|
6809
|
+
const unsafe = /[\u0000-\u001f\u007f\u2028\u2029]/;
|
|
6810
|
+
if (unsafe.test(coauthor.name) || unsafe.test(coauthor.email)) return null;
|
|
6811
|
+
const name = coauthor.name.trim();
|
|
6812
|
+
const email = coauthor.email.trim();
|
|
6813
|
+
if (!name || !email || /[<>]/.test(email)) return null;
|
|
6814
|
+
return `Co-authored-by: ${name} <${email}>`;
|
|
6815
|
+
}
|
|
6667
6816
|
function injectCoauthor(command, coauthor) {
|
|
6668
|
-
|
|
6669
|
-
|
|
6817
|
+
const unchanged = { command, env: {} };
|
|
6818
|
+
if (!coauthor) return unchanged;
|
|
6819
|
+
const trailer = buildCoauthorTrailer(coauthor);
|
|
6820
|
+
if (!trailer) {
|
|
6821
|
+
logger.warn("bash:coauthor_rejected", { reason: "empty value, control character, or line break in co-author name/email" });
|
|
6822
|
+
return unchanged;
|
|
6823
|
+
}
|
|
6670
6824
|
const trimmed = command.trim();
|
|
6671
|
-
if (command.includes(trailer)) return
|
|
6825
|
+
if (command.includes(trailer)) return unchanged;
|
|
6672
6826
|
const createsCommit = /\bgit\s+(commit|merge|revert|cherry-pick)\b/.test(trimmed);
|
|
6673
6827
|
const isRebaseContinue = /\bgit\s+rebase\b/.test(trimmed) && !/\b--abort\b|\b--skip\b/.test(trimmed);
|
|
6674
6828
|
const movesHeadOnly = /\bgit\s+(reset|checkout|switch)\b/.test(trimmed);
|
|
6675
6829
|
const mentionsGit = /\bgit\b/.test(trimmed);
|
|
6676
|
-
if (!createsCommit && !isRebaseContinue && !mentionsGit) return
|
|
6677
|
-
if (movesHeadOnly) return
|
|
6678
|
-
const
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
fi
|
|
6683
|
-
`.trim();
|
|
6830
|
+
if (!createsCommit && !isRebaseContinue && !mentionsGit) return unchanged;
|
|
6831
|
+
if (movesHeadOnly) return unchanged;
|
|
6832
|
+
const env2 = {
|
|
6833
|
+
[COAUTHOR_TRAILER_ENV]: trailer,
|
|
6834
|
+
[COAUTHOR_MSG_ENV]: join14(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
|
|
6835
|
+
};
|
|
6684
6836
|
if (createsCommit || isRebaseContinue) {
|
|
6685
|
-
return `(${command}) && { ${
|
|
6837
|
+
return { command: `(${command}) && { ${AMEND_BLOCK}
|
|
6838
|
+
}`, env: env2 };
|
|
6686
6839
|
}
|
|
6687
6840
|
const beforeHead = `git rev-parse HEAD 2>/dev/null || echo "NO_HEAD"`;
|
|
6688
|
-
const afterCheck =
|
|
6689
|
-
_KF_AFTER_HEAD=$(git rev-parse HEAD 2>/dev/null || echo "NO_HEAD")
|
|
6690
|
-
if [ "$_KF_BEFORE_HEAD" != "$_KF_AFTER_HEAD" ] && [ "$_KF_AFTER_HEAD" != "NO_HEAD" ] && git merge-base --is-ancestor "$_KF_BEFORE_HEAD" "$_KF_AFTER_HEAD" 2>/dev/null; then
|
|
6691
|
-
|
|
6692
|
-
fi
|
|
6693
|
-
|
|
6694
|
-
return
|
|
6841
|
+
const afterCheck = [
|
|
6842
|
+
`_KF_AFTER_HEAD=$(git rev-parse HEAD 2>/dev/null || echo "NO_HEAD")`,
|
|
6843
|
+
`if [ "$_KF_BEFORE_HEAD" != "$_KF_AFTER_HEAD" ] && [ "$_KF_AFTER_HEAD" != "NO_HEAD" ] && git merge-base --is-ancestor "$_KF_BEFORE_HEAD" "$_KF_AFTER_HEAD" 2>/dev/null; then`,
|
|
6844
|
+
AMEND_BLOCK,
|
|
6845
|
+
`fi`
|
|
6846
|
+
].join("\n");
|
|
6847
|
+
return {
|
|
6848
|
+
command: `_KF_BEFORE_HEAD=$(${beforeHead}); (${command}); _KF_EXIT=$?; [ $_KF_EXIT -eq 0 ] && { ${afterCheck}
|
|
6849
|
+
}; exit $_KF_EXIT`,
|
|
6850
|
+
env: env2
|
|
6851
|
+
};
|
|
6695
6852
|
}
|
|
6696
6853
|
async function runBash(args, ctx) {
|
|
6697
6854
|
const timeout = Math.min(Math.max(1e3, args.timeout_ms ?? DEFAULT_TIMEOUT), MAX_TIMEOUT);
|
|
6698
6855
|
const pushGuard = await guardGitPush(args.command, ctx);
|
|
6699
6856
|
if (pushGuard) return pushGuard;
|
|
6700
6857
|
const { shell, args: shellArgs, isPosix } = getShellCommand(ctx.shell);
|
|
6701
|
-
const command = isPosix ? injectCoauthor(args.command, ctx.coauthor) : args.command;
|
|
6858
|
+
const { command, env: coauthorEnv } = isPosix ? injectCoauthor(args.command, ctx.coauthor) : { command: args.command, env: {} };
|
|
6702
6859
|
return new Promise((resolve15, reject) => {
|
|
6703
6860
|
logger.debug("bash:spawn", { command: args.command.slice(0, 200), cwd: ctx.cwd, shell });
|
|
6704
6861
|
const child = spawn(shell, [...shellArgs, command], {
|
|
6705
6862
|
cwd: ctx.cwd,
|
|
6706
6863
|
env: {
|
|
6707
6864
|
...process.env,
|
|
6865
|
+
...coauthorEnv,
|
|
6708
6866
|
GIT_EDITOR: "true"
|
|
6709
6867
|
}
|
|
6710
6868
|
});
|
|
@@ -6771,7 +6929,7 @@ ${stderr}`
|
|
|
6771
6929
|
});
|
|
6772
6930
|
});
|
|
6773
6931
|
}
|
|
6774
|
-
var DEFAULT_TIMEOUT, MAX_TIMEOUT, bashTool;
|
|
6932
|
+
var DEFAULT_TIMEOUT, MAX_TIMEOUT, bashTool, COAUTHOR_TRAILER_ENV, COAUTHOR_MSG_ENV, AMEND_BLOCK;
|
|
6775
6933
|
var init_bash = __esm({
|
|
6776
6934
|
"src/tools/bash.ts"() {
|
|
6777
6935
|
"use strict";
|
|
@@ -6801,6 +6959,14 @@ var init_bash = __esm({
|
|
|
6801
6959
|
render: (args) => ({ title: formatBashTitle(String(args.command ?? "")) }),
|
|
6802
6960
|
run: (args, ctx) => runBash(args, ctx)
|
|
6803
6961
|
};
|
|
6962
|
+
COAUTHOR_TRAILER_ENV = "KF_COAUTHOR_TRAILER";
|
|
6963
|
+
COAUTHOR_MSG_ENV = "KF_COAUTHOR_MSG";
|
|
6964
|
+
AMEND_BLOCK = [
|
|
6965
|
+
`if ! git log -1 --pretty=%B 2>/dev/null | grep -qF -- "$${COAUTHOR_TRAILER_ENV}"; then`,
|
|
6966
|
+
` git log -1 --pretty=%B | git interpret-trailers --trailer "$${COAUTHOR_TRAILER_ENV}" > "$${COAUTHOR_MSG_ENV}" && git commit --amend -F "$${COAUTHOR_MSG_ENV}" --no-edit; _KF_RC=$?;`,
|
|
6967
|
+
` rm -f "$${COAUTHOR_MSG_ENV}"; [ $_KF_RC -eq 0 ];`,
|
|
6968
|
+
`fi`
|
|
6969
|
+
].join("\n");
|
|
6804
6970
|
}
|
|
6805
6971
|
});
|
|
6806
6972
|
|
|
@@ -8436,6 +8602,8 @@ async function runHotcellWorker(options) {
|
|
|
8436
8602
|
throw new Error("Hotcell worker spend cap must be a positive number.");
|
|
8437
8603
|
}
|
|
8438
8604
|
if (options.signal?.aborted) return terminalResult(workerId, options.task, "cancelled", "Cancelled before worker setup.");
|
|
8605
|
+
const workerPackage = resolveWorkerPackage(options.workerPackage);
|
|
8606
|
+
const maxInputTokens = resolveWorkerInputBudget(model, options.budgetUsd, options.maxInputTokens);
|
|
8439
8607
|
await acquireSlot(options.maxParallel ?? MAX_PARALLEL_WORKERS, options.signal);
|
|
8440
8608
|
let cellId;
|
|
8441
8609
|
let result2;
|
|
@@ -8487,9 +8655,43 @@ async function runHotcellWorker(options) {
|
|
|
8487
8655
|
result2 = terminalResult(workerId, options.task, "failed", "Hotcell has no OpenRouter gateway route. Run `hotcell keys add openrouter` on the daemon host, then retry.", model);
|
|
8488
8656
|
return result2;
|
|
8489
8657
|
}
|
|
8658
|
+
const setupCommand = [
|
|
8659
|
+
...FIND_REPO_ROOT,
|
|
8660
|
+
`git -C "$REPO_ROOT" fetch --quiet origin ${shellQuote(repo.commit)}`,
|
|
8661
|
+
`git -C "$REPO_ROOT" checkout --quiet --detach ${shellQuote(repo.commit)}`,
|
|
8662
|
+
`npm install --prefix ${WORKER_PREFIX} --no-audit --no-fund --omit=optional --loglevel=error ${shellQuote(workerPackage)} 1>&2`
|
|
8663
|
+
].join(" && ");
|
|
8664
|
+
const setup = await execute(command, ["exec", cellId, setupCommand, "--cwd", "/workspace"], {
|
|
8665
|
+
cwd,
|
|
8666
|
+
signal: options.signal,
|
|
8667
|
+
timeoutMs: options.setupTimeoutMs ?? DEFAULT_SETUP_TIMEOUT_MS
|
|
8668
|
+
});
|
|
8669
|
+
if (setup.timedOut || setup.aborted) {
|
|
8670
|
+
result2 = terminalResult(
|
|
8671
|
+
workerId,
|
|
8672
|
+
options.task,
|
|
8673
|
+
options.signal?.aborted ? "cancelled" : "timed_out",
|
|
8674
|
+
options.signal?.aborted ? "Cancelled during worker setup." : `Worker setup (checkout and ${workerPackage} install) timed out.`,
|
|
8675
|
+
model
|
|
8676
|
+
);
|
|
8677
|
+
return result2;
|
|
8678
|
+
}
|
|
8679
|
+
if (setup.code !== 0) {
|
|
8680
|
+
const detail = sanitizeHotcellDiagnostic(`${setup.stderr}
|
|
8681
|
+
${setup.stdout}`);
|
|
8682
|
+
result2 = terminalResult(
|
|
8683
|
+
workerId,
|
|
8684
|
+
options.task,
|
|
8685
|
+
"failed",
|
|
8686
|
+
`Worker setup failed (exit ${setup.code}) while checking out the commit or installing ${workerPackage}${detail ? `: ${detail}` : "."}`,
|
|
8687
|
+
model
|
|
8688
|
+
);
|
|
8689
|
+
return result2;
|
|
8690
|
+
}
|
|
8490
8691
|
const prompt = [
|
|
8491
8692
|
"You are an isolated, read-only research worker. You cannot edit files, run shell commands, push, publish, or call write-capable integrations.",
|
|
8492
8693
|
"Use the exact requested model and return concise findings, recommendations, and relevant file paths. Do not claim to have changed files.",
|
|
8694
|
+
WORKER_REPORT_INSTRUCTIONS,
|
|
8493
8695
|
`Mission:
|
|
8494
8696
|
${options.task}`,
|
|
8495
8697
|
options.context ? `Coordinator context:
|
|
@@ -8497,16 +8699,11 @@ ${options.context}` : ""
|
|
|
8497
8699
|
].filter(Boolean).join("\n\n");
|
|
8498
8700
|
const encodedPrompt = Buffer.from(prompt, "utf8").toString("base64");
|
|
8499
8701
|
const shellCommand = [
|
|
8500
|
-
|
|
8501
|
-
'test -n "$GIT_DIR"',
|
|
8502
|
-
'REPO_ROOT="${GIT_DIR%/.git}"',
|
|
8503
|
-
`git -C "$REPO_ROOT" fetch --quiet origin ${shellQuote(repo.commit)}`,
|
|
8504
|
-
`git -C "$REPO_ROOT" checkout --quiet --detach ${shellQuote(repo.commit)}`,
|
|
8702
|
+
...FIND_REPO_ROOT,
|
|
8505
8703
|
'cd "$REPO_ROOT"',
|
|
8506
|
-
"npm ci --no-audit --no-fund --loglevel=error 1>&2",
|
|
8507
8704
|
'export OPENROUTER_BASE_URL="${OPENROUTER_BASE_URL%/}/v1"',
|
|
8508
8705
|
`PROMPT="$(printf '%s' '${encodedPrompt}' | base64 -d)"`,
|
|
8509
|
-
|
|
8706
|
+
`${WORKER_BIN} --format json --max-input-tokens ${maxInputTokens} --model ${shellQuote(model)} --worker-profile research -p "$PROMPT"`
|
|
8510
8707
|
].join(" && ");
|
|
8511
8708
|
const execution = await execute(command, ["exec", cellId, shellCommand, "--cwd", "/workspace"], {
|
|
8512
8709
|
cwd,
|
|
@@ -8524,14 +8721,16 @@ ${execution.stderr}`.toLowerCase();
|
|
|
8524
8721
|
const executionDiagnostic = sanitizeHotcellDiagnostic(execution.stderr);
|
|
8525
8722
|
const spendExhausted = /(?:http\s*)?402|spend cap|spending limit/.test(diagnostic);
|
|
8526
8723
|
const status = execution.timedOut ? "timed_out" : execution.aborted ? options.signal?.aborted ? "cancelled" : "failed" : spendExhausted ? "spend_exhausted" : budgetExhausted ? "budget_exhausted" : execution.code === 0 ? "completed" : "failed";
|
|
8527
|
-
const
|
|
8724
|
+
const report = parseWorkerReport(summary);
|
|
8725
|
+
const findings = report ? report.findings : summary ? [{ topic: "Research findings", summary: summary.slice(0, 4e4), confidence: "medium", sources: [], relevance: "high" }] : [];
|
|
8528
8726
|
result2 = {
|
|
8529
8727
|
workerId,
|
|
8530
8728
|
status,
|
|
8531
8729
|
task: options.task,
|
|
8532
8730
|
findings,
|
|
8533
8731
|
recommendations: [],
|
|
8534
|
-
filesRead: [],
|
|
8732
|
+
filesRead: report?.filesRead ?? [],
|
|
8733
|
+
...report ? { structured: true, openQuestions: report.openQuestions } : {},
|
|
8535
8734
|
webSources: [],
|
|
8536
8735
|
costUsd: metrics.costUsd ?? 0,
|
|
8537
8736
|
tokensUsed: metrics.tokensUsed ?? tokensUsed,
|
|
@@ -8582,8 +8781,60 @@ async function getCleanRepository(cwd) {
|
|
|
8582
8781
|
if (!/^(https:\/\/|git@|ssh:\/\/)/i.test(url) || /:\/\/[^/]*@/.test(url)) {
|
|
8583
8782
|
throw new Error("Hotcell workers require a credential-free HTTPS or SSH origin URL that the Hotcell daemon can clone.");
|
|
8584
8783
|
}
|
|
8784
|
+
const pushed = (await execFileAsync("git", ["branch", "-r", "--contains", commit], { cwd: root })).stdout.trim();
|
|
8785
|
+
if (!pushed) {
|
|
8786
|
+
throw new Error(
|
|
8787
|
+
`Hotcell workers clone from origin, but HEAD (${commit.slice(0, 8)}) is not on any remote-tracking branch. Push it first (git push -u origin ${ref}), or run git fetch if it was pushed elsewhere, then retry.`
|
|
8788
|
+
);
|
|
8789
|
+
}
|
|
8585
8790
|
return { url, commit, ref };
|
|
8586
8791
|
}
|
|
8792
|
+
function resolveWorkerInputBudget(model, budgetUsd, override) {
|
|
8793
|
+
const clamp = (n) => Math.min(MAX_WORKER_INPUT_TOKENS, Math.max(MIN_WORKER_INPUT_TOKENS, Math.floor(n)));
|
|
8794
|
+
const envOverride = Number(process.env.KIMIFLARE_WORKER_MAX_INPUT_TOKENS);
|
|
8795
|
+
const explicit = override ?? (Number.isFinite(envOverride) && envOverride > 0 ? envOverride : void 0);
|
|
8796
|
+
if (explicit !== void 0 && Number.isFinite(explicit) && explicit > 0) return clamp(explicit);
|
|
8797
|
+
const perMillion = getModelOrInfer(model).pricing.inputPerMtok;
|
|
8798
|
+
if (!(perMillion > 0) || !(budgetUsd > 0)) return DEFAULT_WORKER_INPUT_TOKENS;
|
|
8799
|
+
return clamp(budgetUsd * INPUT_SHARE_OF_BUDGET * 1e6 / perMillion);
|
|
8800
|
+
}
|
|
8801
|
+
function parseWorkerReport(text) {
|
|
8802
|
+
const blocks = [...text.matchAll(/```json\s*\n([\s\S]*?)```/g)];
|
|
8803
|
+
const raw = blocks.at(-1)?.[1];
|
|
8804
|
+
if (!raw) return null;
|
|
8805
|
+
let parsed;
|
|
8806
|
+
try {
|
|
8807
|
+
parsed = JSON.parse(raw);
|
|
8808
|
+
} catch {
|
|
8809
|
+
return null;
|
|
8810
|
+
}
|
|
8811
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.findings)) return null;
|
|
8812
|
+
const obj = parsed;
|
|
8813
|
+
const strings = (value, maxChars = 300) => Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.trim().length > 0).slice(0, MAX_REPORT_LIST_ITEMS).map((v) => v.trim().slice(0, maxChars)) : [];
|
|
8814
|
+
const findings = [];
|
|
8815
|
+
for (const item of obj.findings.slice(0, MAX_REPORT_FINDINGS)) {
|
|
8816
|
+
if (!item || typeof item !== "object") continue;
|
|
8817
|
+
const f = item;
|
|
8818
|
+
if (typeof f.summary !== "string" || !f.summary.trim()) continue;
|
|
8819
|
+
const confidence = f.confidence === "high" || f.confidence === "low" ? f.confidence : "medium";
|
|
8820
|
+
findings.push({
|
|
8821
|
+
topic: typeof f.topic === "string" && f.topic.trim() ? f.topic.trim().slice(0, 120) : "Finding",
|
|
8822
|
+
summary: f.summary.trim().slice(0, MAX_REPORT_SUMMARY_CHARS),
|
|
8823
|
+
confidence,
|
|
8824
|
+
sources: strings(f.files),
|
|
8825
|
+
relevance: "high"
|
|
8826
|
+
});
|
|
8827
|
+
}
|
|
8828
|
+
if (findings.length === 0) return null;
|
|
8829
|
+
return { findings, openQuestions: strings(obj.openQuestions, 500), filesRead: strings(obj.filesRead) };
|
|
8830
|
+
}
|
|
8831
|
+
function resolveWorkerPackage(override) {
|
|
8832
|
+
const spec = (override ?? process.env.KIMIFLARE_WORKER_PACKAGE ?? `${PACKAGE_NAME}@${getAppVersion()}`).trim();
|
|
8833
|
+
if (!spec || !PACKAGE_SPEC.test(spec)) {
|
|
8834
|
+
throw new Error(`Invalid Hotcell worker package spec: ${JSON.stringify(spec)}.`);
|
|
8835
|
+
}
|
|
8836
|
+
return spec;
|
|
8837
|
+
}
|
|
8587
8838
|
function parseCellId(output) {
|
|
8588
8839
|
const uuid = output.match(/\b[a-f0-9]{8}(?:-[a-f0-9]{4,}){0,4}\b/gi)?.at(-1);
|
|
8589
8840
|
if (uuid) return uuid;
|
|
@@ -8734,22 +8985,65 @@ function releaseSlot() {
|
|
|
8734
8985
|
runningWorkers = Math.max(0, runningWorkers - 1);
|
|
8735
8986
|
for (const wake of workerQueue.slice()) wake();
|
|
8736
8987
|
}
|
|
8737
|
-
var execFileAsync, MAX_OUTPUT_BYTES, MAX_PROMPT_CHARS, DEFAULT_CELL_TIMEOUT_MS, MAX_PARALLEL_WORKERS, runningWorkers, workerQueue;
|
|
8988
|
+
var execFileAsync, MAX_OUTPUT_BYTES, MAX_PROMPT_CHARS, DEFAULT_CELL_TIMEOUT_MS, DEFAULT_SETUP_TIMEOUT_MS, WORKER_PREFIX, WORKER_BIN, PACKAGE_SPEC, FIND_REPO_ROOT, MAX_PARALLEL_WORKERS, MIN_WORKER_INPUT_TOKENS, MAX_WORKER_INPUT_TOKENS, DEFAULT_WORKER_INPUT_TOKENS, INPUT_SHARE_OF_BUDGET, MAX_REPORT_FINDINGS, MAX_REPORT_SUMMARY_CHARS, MAX_REPORT_LIST_ITEMS, runningWorkers, workerQueue, WORKER_REPORT_INSTRUCTIONS;
|
|
8738
8989
|
var init_hotcell_worker = __esm({
|
|
8739
8990
|
"src/tools/hotcell-worker.ts"() {
|
|
8740
8991
|
"use strict";
|
|
8741
8992
|
init_client();
|
|
8993
|
+
init_version();
|
|
8994
|
+
init_registry();
|
|
8742
8995
|
execFileAsync = promisify2(execFile2);
|
|
8743
8996
|
MAX_OUTPUT_BYTES = 1e6;
|
|
8744
8997
|
MAX_PROMPT_CHARS = 4e4;
|
|
8745
8998
|
DEFAULT_CELL_TIMEOUT_MS = 3e5;
|
|
8999
|
+
DEFAULT_SETUP_TIMEOUT_MS = 3e5;
|
|
9000
|
+
WORKER_PREFIX = "/tmp/autopilot-worker";
|
|
9001
|
+
WORKER_BIN = `${WORKER_PREFIX}/node_modules/.bin/${CLI_NAME}`;
|
|
9002
|
+
PACKAGE_SPEC = /^[\w@./:+~^=<>#-]+$/;
|
|
9003
|
+
FIND_REPO_ROOT = [
|
|
9004
|
+
'GIT_DIR="$(find /workspace -mindepth 2 -maxdepth 5 -name .git -print -quit)"',
|
|
9005
|
+
'test -n "$GIT_DIR"',
|
|
9006
|
+
'REPO_ROOT="${GIT_DIR%/.git}"'
|
|
9007
|
+
];
|
|
8746
9008
|
MAX_PARALLEL_WORKERS = 3;
|
|
9009
|
+
MIN_WORKER_INPUT_TOKENS = 6e4;
|
|
9010
|
+
MAX_WORKER_INPUT_TOKENS = 3e6;
|
|
9011
|
+
DEFAULT_WORKER_INPUT_TOKENS = 4e5;
|
|
9012
|
+
INPUT_SHARE_OF_BUDGET = 0.8;
|
|
9013
|
+
MAX_REPORT_FINDINGS = 12;
|
|
9014
|
+
MAX_REPORT_SUMMARY_CHARS = 2e3;
|
|
9015
|
+
MAX_REPORT_LIST_ITEMS = 25;
|
|
8747
9016
|
runningWorkers = 0;
|
|
8748
9017
|
workerQueue = [];
|
|
9018
|
+
WORKER_REPORT_INSTRUCTIONS = [
|
|
9019
|
+
"Finish your answer with one fenced ```json block, and nothing after it, of exactly this shape:",
|
|
9020
|
+
'{"findings":[{"topic":"short title","summary":"what you found and why it matters","files":["path/to/file.ts:123"],"confidence":"high|medium|low"}],"openQuestions":["what you could not determine"],"filesRead":["path/to/file.ts"]}',
|
|
9021
|
+
"Cite repository-relative file paths (with line numbers where useful) for every finding. Use low confidence for anything you did not verify in the code."
|
|
9022
|
+
].join("\n");
|
|
8749
9023
|
}
|
|
8750
9024
|
});
|
|
8751
9025
|
|
|
8752
9026
|
// src/tools/spawn-worker.ts
|
|
9027
|
+
function formatWorkerResult(result2, model) {
|
|
9028
|
+
const lines = [
|
|
9029
|
+
"Local Hotcell worker " + result2.status + (result2.status === "budget_exhausted" ? " (partial result)" : "") + ".",
|
|
9030
|
+
"Model: " + (result2.model ?? model) + " \xB7 Tokens: " + result2.tokensUsed.toLocaleString() + " \xB7 Cost: " + (result2.costUsd > 0 ? result2.costUsd.toFixed(4) + " total Hotcell cost" : "unavailable from Hotcell stats") + ".",
|
|
9031
|
+
...result2.findings.map((finding) => "\n## " + finding.topic + (result2.structured ? " (" + finding.confidence + " confidence)" : "") + "\n" + finding.summary + (finding.sources.length ? "\nFiles: " + finding.sources.join(", ") : ""))
|
|
9032
|
+
];
|
|
9033
|
+
if (result2.openQuestions?.length) {
|
|
9034
|
+
lines.push("\n## Open questions\n" + result2.openQuestions.map((q) => "- " + q).join("\n"));
|
|
9035
|
+
}
|
|
9036
|
+
if (result2.filesRead.length) {
|
|
9037
|
+
lines.push("\nFiles read: " + result2.filesRead.join(", "));
|
|
9038
|
+
}
|
|
9039
|
+
if (!result2.structured && result2.findings.length) {
|
|
9040
|
+
lines.push("\n(Worker did not return a structured report; findings above are its raw answer and are unverified.)");
|
|
9041
|
+
}
|
|
9042
|
+
if (result2.status === "budget_exhausted") {
|
|
9043
|
+
lines.push("\nWorker input-token budget was exhausted; review partial findings before relying on them.");
|
|
9044
|
+
}
|
|
9045
|
+
return lines.join("\n");
|
|
9046
|
+
}
|
|
8753
9047
|
function textOutput(content) {
|
|
8754
9048
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
8755
9049
|
return { content, rawBytes: bytes, reducedBytes: bytes };
|
|
@@ -8771,7 +9065,8 @@ var init_spawn_worker = __esm({
|
|
|
8771
9065
|
name: "spawn_worker",
|
|
8772
9066
|
description: [
|
|
8773
9067
|
"Spawn a read-only research worker in a local Hotcell sandbox. Hotcell is the only worker backend.",
|
|
8774
|
-
"Workers inspect the committed repository snapshot and cannot edit files; implement any changes in the coordinator."
|
|
9068
|
+
"Workers inspect the committed repository snapshot and cannot edit files; implement any changes in the coordinator.",
|
|
9069
|
+
"To run several workers in parallel, emit all of their spawn_worker calls in the same response; the user approves them together."
|
|
8775
9070
|
].join(" "),
|
|
8776
9071
|
parameters: {
|
|
8777
9072
|
type: "object",
|
|
@@ -8805,10 +9100,22 @@ var init_spawn_worker = __esm({
|
|
|
8805
9100
|
additionalProperties: false
|
|
8806
9101
|
},
|
|
8807
9102
|
needsPermission: true,
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
9103
|
+
// Workers run in isolated Hotcell cells and never touch the workspace, so
|
|
9104
|
+
// several can run at once; the Hotcell slot semaphore bounds concurrency.
|
|
9105
|
+
concurrent: true,
|
|
9106
|
+
render: (args) => {
|
|
9107
|
+
const batch = args.batch;
|
|
9108
|
+
if (Array.isArray(batch) && batch.length > 1) {
|
|
9109
|
+
return {
|
|
9110
|
+
title: `spawn_worker \xD7 ${batch.length} (Hotcell plan, run in parallel)`,
|
|
9111
|
+
body: batch.map((item, i) => `${i + 1}. ${String(item.task ?? "").slice(0, 160)}`).join("\n")
|
|
9112
|
+
};
|
|
9113
|
+
}
|
|
9114
|
+
return {
|
|
9115
|
+
title: "spawn_worker (Hotcell plan)",
|
|
9116
|
+
body: args.task.slice(0, 200)
|
|
9117
|
+
};
|
|
9118
|
+
},
|
|
8812
9119
|
async run(args, ctx) {
|
|
8813
9120
|
const cfg = await loadConfig().catch(() => null);
|
|
8814
9121
|
if (!cfg?.openrouterApiKey) {
|
|
@@ -8836,6 +9143,7 @@ var init_spawn_worker = __esm({
|
|
|
8836
9143
|
model,
|
|
8837
9144
|
budgetUsd,
|
|
8838
9145
|
timeoutMs,
|
|
9146
|
+
setupTimeoutMs: readNumberEnv2("KIMIFLARE_WORKER_SETUP_TIMEOUT_MS"),
|
|
8839
9147
|
maxParallel: cfg.workerMaxParallel,
|
|
8840
9148
|
cwd: ctx.cwd,
|
|
8841
9149
|
signal: ctx.signal
|
|
@@ -8846,15 +9154,7 @@ var init_spawn_worker = __esm({
|
|
|
8846
9154
|
if (result2.status !== "completed" && result2.status !== "budget_exhausted") {
|
|
8847
9155
|
throw new Error("Local Hotcell worker " + result2.status + ": " + (result2.error ?? "unknown error"));
|
|
8848
9156
|
}
|
|
8849
|
-
|
|
8850
|
-
"Local Hotcell worker " + result2.status + (result2.status === "budget_exhausted" ? " (partial result)" : "") + ".",
|
|
8851
|
-
"Model: " + (result2.model ?? model) + " \xB7 Tokens: " + result2.tokensUsed.toLocaleString() + " \xB7 Cost: " + (result2.costUsd > 0 ? result2.costUsd.toFixed(4) + " total Hotcell cost" : "unavailable from Hotcell stats") + ".",
|
|
8852
|
-
...result2.findings.map((finding) => "\n## " + finding.topic + "\n" + finding.summary)
|
|
8853
|
-
];
|
|
8854
|
-
if (result2.status === "budget_exhausted") {
|
|
8855
|
-
lines.push("\nWorker input-token budget was exhausted; review partial findings before relying on them.");
|
|
8856
|
-
}
|
|
8857
|
-
return textOutput(lines.join("\n"));
|
|
9157
|
+
return textOutput(formatWorkerResult(result2, model));
|
|
8858
9158
|
}
|
|
8859
9159
|
};
|
|
8860
9160
|
}
|
|
@@ -10206,9 +10506,9 @@ var init_store = __esm({
|
|
|
10206
10506
|
throw new Error(`Invalid run transition: ${current.status} -> ${status}`);
|
|
10207
10507
|
}
|
|
10208
10508
|
const now2 = Date.now();
|
|
10209
|
-
const
|
|
10509
|
+
const startedAt2 = status === "running" ? current.startedAt ?? now2 : current.startedAt;
|
|
10210
10510
|
const finishedAt = ["completed", "failed", "cancelled", "interrupted_unknown"].includes(status) ? now2 : null;
|
|
10211
|
-
this.db.prepare(`UPDATE runs SET status = ?, stop_reason = ?, updated_at = ?, started_at = ?, finished_at = ? WHERE id = ?`).run(status, reason ?? null, now2,
|
|
10511
|
+
this.db.prepare(`UPDATE runs SET status = ?, stop_reason = ?, updated_at = ?, started_at = ?, finished_at = ? WHERE id = ?`).run(status, reason ?? null, now2, startedAt2, finishedAt, id);
|
|
10212
10512
|
this.appendEvent(id, "state", null, null, { status, reason: reason ?? null }, now2);
|
|
10213
10513
|
return this.getRun(id);
|
|
10214
10514
|
});
|
|
@@ -11410,13 +11710,13 @@ function directiveFor(kind) {
|
|
|
11410
11710
|
return "The user explicitly asked not to delegate. Do not call spawn_worker; handle the task in the main session and respect any stated dependencies.";
|
|
11411
11711
|
}
|
|
11412
11712
|
if (kind === "explicit-delegate") {
|
|
11413
|
-
return "The user explicitly requested subagents. Before acting, split the request into bounded missions that can make progress independently; use spawn_worker in plan mode for useful parallel read-only research, wait for the workers, and synthesize their findings before dependent implementation. Do not delegate tightly coupled or sequential steps. The tool's normal permission prompt and all configured safety limits still apply.";
|
|
11713
|
+
return "The user explicitly requested subagents. Before acting, split the request into bounded missions that can make progress independently; use spawn_worker in plan mode for useful parallel read-only research (emit all independent spawn_worker calls in the same response so they run concurrently), wait for the workers, and synthesize their findings before dependent implementation. Do not delegate tightly coupled or sequential steps. The tool's normal permission prompt and all configured safety limits still apply.";
|
|
11414
11714
|
}
|
|
11415
11715
|
if (kind === "suggest") {
|
|
11416
11716
|
return "The harness sees a possible parallel-work opportunity, but the user did not request delegation. Use spawn_worker only if you can define bounded, independent missions that materially help. The tool's normal permission prompt is the required user confirmation; never assume approval. Keep dependent implementation in the coordinator.";
|
|
11417
11717
|
}
|
|
11418
11718
|
if (kind === "auto-delegate") {
|
|
11419
|
-
return "Proactive subagent delegation is enabled for this substantial task. First assess its structure, dependencies, and coordination cost. If two or more bounded research tracks can make meaningful progress independently, delegate them with spawn_worker in plan mode in
|
|
11719
|
+
return "Proactive subagent delegation is enabled for this substantial task. First assess its structure, dependencies, and coordination cost. If two or more bounded research tracks can make meaningful progress independently, delegate them with spawn_worker in plan mode by emitting all of those calls in the same response so they run concurrently, wait for their results, and synthesize the findings before doing dependent work in the coordinator. Keep trivial, tightly coupled, and sequential work local. Worker calls still require normal permission approval (one prompt covers a batch) and local Hotcell with OpenRouter, a clean pushed repository, spend cap, concurrency, timeout, and read-only restrictions; never bypass them. If a worker cannot run or fails, explain that plainly and continue locally without implying delegation succeeded.";
|
|
11420
11720
|
}
|
|
11421
11721
|
return void 0;
|
|
11422
11722
|
}
|
|
@@ -15273,6 +15573,7 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
15273
15573
|
return "deny";
|
|
15274
15574
|
}
|
|
15275
15575
|
};
|
|
15576
|
+
const compaction = sessionFileCompactionTarget(sessionFile);
|
|
15276
15577
|
try {
|
|
15277
15578
|
if (run && runStore) {
|
|
15278
15579
|
const tools = ALL_TOOLS.filter((tool) => active.allowedTools?.has(tool.name));
|
|
@@ -15292,6 +15593,7 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
15292
15593
|
codeMode: false,
|
|
15293
15594
|
allowDirectPush: config2.allowDirectPush,
|
|
15294
15595
|
preferPullRequests: config2.preferPullRequests,
|
|
15596
|
+
compaction,
|
|
15295
15597
|
callbacks,
|
|
15296
15598
|
onIterationEnd: async (updatedMessages) => {
|
|
15297
15599
|
sessionFile.messages = updatedMessages;
|
|
@@ -15318,6 +15620,7 @@ async function runAgentTurnForSession(active, config2, allowAll) {
|
|
|
15318
15620
|
codeMode: config2.codeMode,
|
|
15319
15621
|
allowDirectPush: config2.allowDirectPush,
|
|
15320
15622
|
preferPullRequests: config2.preferPullRequests,
|
|
15623
|
+
compaction,
|
|
15321
15624
|
callbacks
|
|
15322
15625
|
});
|
|
15323
15626
|
}
|
|
@@ -15374,6 +15677,7 @@ var init_routes = __esm({
|
|
|
15374
15677
|
init_system_prompt();
|
|
15375
15678
|
init_executor();
|
|
15376
15679
|
init_sessions();
|
|
15680
|
+
init_context_budget();
|
|
15377
15681
|
init_logger();
|
|
15378
15682
|
init_sse2();
|
|
15379
15683
|
init_openapi();
|
|
@@ -17913,6 +18217,7 @@ async function createAgentSession(opts2) {
|
|
|
17913
18217
|
{ role: "system", content: systemPrompt },
|
|
17914
18218
|
...sessionFile.messages.filter((m) => m.role !== "system")
|
|
17915
18219
|
];
|
|
18220
|
+
const isNewSession = sessionFile.messages.length === 0;
|
|
17916
18221
|
const session = new InternalSession({
|
|
17917
18222
|
sessionFile,
|
|
17918
18223
|
cwd,
|
|
@@ -17928,6 +18233,11 @@ async function createAgentSession(opts2) {
|
|
|
17928
18233
|
hooks,
|
|
17929
18234
|
provider: opts2.provider
|
|
17930
18235
|
});
|
|
18236
|
+
if (isNewSession) {
|
|
18237
|
+
await session.save().catch((err) => {
|
|
18238
|
+
logger.warn("sdk:initial_save_failed", { sessionId: session.sessionId, error: err.message });
|
|
18239
|
+
});
|
|
18240
|
+
}
|
|
17931
18241
|
return { session };
|
|
17932
18242
|
}
|
|
17933
18243
|
function costLookup(config2, meta) {
|
|
@@ -17951,6 +18261,7 @@ var init_session = __esm({
|
|
|
17951
18261
|
init_requesty_catalog();
|
|
17952
18262
|
init_custom_endpoint();
|
|
17953
18263
|
init_logger();
|
|
18264
|
+
init_context_budget();
|
|
17954
18265
|
init_config2();
|
|
17955
18266
|
init_permissions();
|
|
17956
18267
|
InternalSession = class {
|
|
@@ -17986,8 +18297,15 @@ var init_session = __esm({
|
|
|
17986
18297
|
turnCount: 0
|
|
17987
18298
|
};
|
|
17988
18299
|
disposed = false;
|
|
18300
|
+
createdAt;
|
|
18301
|
+
/** Persisted compaction state (session state + archived artifacts). */
|
|
18302
|
+
compactionState;
|
|
18303
|
+
compaction;
|
|
17989
18304
|
constructor(opts2) {
|
|
17990
18305
|
this.sessionId = opts2.sessionFile.id;
|
|
18306
|
+
this.createdAt = opts2.sessionFile.createdAt;
|
|
18307
|
+
this.compactionState = { sessionState: opts2.sessionFile.sessionState, artifactStore: opts2.sessionFile.artifactStore };
|
|
18308
|
+
this.compaction = sessionFileCompactionTarget(this.compactionState);
|
|
17991
18309
|
this.cwd = opts2.cwd;
|
|
17992
18310
|
this.messages = opts2.messages;
|
|
17993
18311
|
this.config = opts2.config;
|
|
@@ -18059,15 +18377,12 @@ var init_session = __esm({
|
|
|
18059
18377
|
this.isStreaming = true;
|
|
18060
18378
|
this.emit({ type: "status", status: "streaming" });
|
|
18061
18379
|
this.abortController = new AbortController();
|
|
18380
|
+
let succeeded = false;
|
|
18062
18381
|
try {
|
|
18063
18382
|
await this.runTurn(mode, options?.maxToolIterations);
|
|
18064
|
-
|
|
18065
|
-
this.messages.push({ role: "user", content: followUp });
|
|
18066
|
-
}
|
|
18067
|
-
this.followUpQueue = [];
|
|
18383
|
+
succeeded = true;
|
|
18068
18384
|
this.isStreaming = false;
|
|
18069
18385
|
this.emit({ type: "status", status: "idle" });
|
|
18070
|
-
await this.save();
|
|
18071
18386
|
} catch (err) {
|
|
18072
18387
|
this.isStreaming = false;
|
|
18073
18388
|
if (err.name === "AbortError") {
|
|
@@ -18082,6 +18397,22 @@ var init_session = __esm({
|
|
|
18082
18397
|
this.emit({ type: "status", status: "error" });
|
|
18083
18398
|
throw err;
|
|
18084
18399
|
}
|
|
18400
|
+
} finally {
|
|
18401
|
+
await this.save().catch((saveErr) => {
|
|
18402
|
+
logger.warn("sdk:save_failed", { sessionId: this.sessionId, error: saveErr.message });
|
|
18403
|
+
});
|
|
18404
|
+
}
|
|
18405
|
+
if (succeeded) await this.runFollowUps(mode);
|
|
18406
|
+
}
|
|
18407
|
+
/** Run queued follow-ups as their own turns, in order. Steers that arrived
|
|
18408
|
+
* after the turn's last chance to see them become follow-ups too. */
|
|
18409
|
+
async runFollowUps(mode) {
|
|
18410
|
+
if (this.steerQueue.length > 0) {
|
|
18411
|
+
this.followUpQueue.unshift(...this.steerQueue.splice(0));
|
|
18412
|
+
}
|
|
18413
|
+
while (this.followUpQueue.length > 0 && !this.disposed) {
|
|
18414
|
+
const next = this.followUpQueue.shift();
|
|
18415
|
+
await this.prompt(next, { mode });
|
|
18085
18416
|
}
|
|
18086
18417
|
}
|
|
18087
18418
|
async steer(text) {
|
|
@@ -18134,9 +18465,11 @@ var init_session = __esm({
|
|
|
18134
18465
|
id: this.sessionId,
|
|
18135
18466
|
cwd: this.cwd,
|
|
18136
18467
|
model: this.model,
|
|
18137
|
-
createdAt:
|
|
18468
|
+
createdAt: this.createdAt,
|
|
18138
18469
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18139
|
-
messages: this.messages
|
|
18470
|
+
messages: this.messages,
|
|
18471
|
+
sessionState: this.compactionState.sessionState,
|
|
18472
|
+
artifactStore: this.compactionState.artifactStore
|
|
18140
18473
|
});
|
|
18141
18474
|
}
|
|
18142
18475
|
dispose() {
|
|
@@ -18155,6 +18488,15 @@ var init_session = __esm({
|
|
|
18155
18488
|
email: this.config.coauthorEmail || "kimiflare@proton.me"
|
|
18156
18489
|
} : void 0;
|
|
18157
18490
|
const callbacks = {
|
|
18491
|
+
onCompacted: (info) => {
|
|
18492
|
+
this.emit({
|
|
18493
|
+
type: "context.compacted",
|
|
18494
|
+
tokensBefore: info.tokensBefore,
|
|
18495
|
+
tokensAfter: info.tokensAfter,
|
|
18496
|
+
turnsRemoved: info.turnsRemoved,
|
|
18497
|
+
artifactsArchived: info.artifactsArchived
|
|
18498
|
+
});
|
|
18499
|
+
},
|
|
18158
18500
|
onAssistantStart: () => {
|
|
18159
18501
|
const messageId = `msg_${this.nextMessageId++}`;
|
|
18160
18502
|
this.currentAssistantMessageId = messageId;
|
|
@@ -18286,14 +18628,19 @@ var init_session = __esm({
|
|
|
18286
18628
|
coauthor,
|
|
18287
18629
|
sessionId: this.sessionId,
|
|
18288
18630
|
memoryManager: this.memoryManager,
|
|
18631
|
+
compaction: this.compaction,
|
|
18289
18632
|
allowDirectPush: this.config.allowDirectPush,
|
|
18290
18633
|
preferPullRequests: this.config.preferPullRequests,
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18634
|
+
// Steering lands at the next tool boundary, or before the final answer
|
|
18635
|
+
// ends the turn (the loop continues so the model can respond to it).
|
|
18636
|
+
drainPendingInput: () => {
|
|
18637
|
+
const steers = this.steerQueue.splice(0);
|
|
18638
|
+
return steers.map((text) => {
|
|
18639
|
+
const messageId = `msg_${this.nextMessageId++}`;
|
|
18640
|
+
this.emit({ type: "message.start", messageId, role: "user" });
|
|
18641
|
+
this.emit({ type: "message.end", messageId });
|
|
18642
|
+
return { role: "user", content: text };
|
|
18643
|
+
});
|
|
18297
18644
|
},
|
|
18298
18645
|
onFileChange: (path, content) => {
|
|
18299
18646
|
if (content) {
|
|
@@ -23910,14 +24257,23 @@ var init_abort_scope = __esm({
|
|
|
23910
24257
|
});
|
|
23911
24258
|
|
|
23912
24259
|
// src/ui/spinner.tsx
|
|
24260
|
+
import { performance as performance2 } from "perf_hooks";
|
|
23913
24261
|
import { useSyncExternalStore } from "react";
|
|
23914
24262
|
import { Text as Text7 } from "ink";
|
|
23915
24263
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
24264
|
+
function animationTickAt(start, now2) {
|
|
24265
|
+
return Math.max(0, Math.floor((now2 - start) / ANIMATION_TICK_MS));
|
|
24266
|
+
}
|
|
23916
24267
|
function subscribe(listener) {
|
|
23917
24268
|
listeners.add(listener);
|
|
23918
24269
|
if (!timer2) {
|
|
24270
|
+
tick = 0;
|
|
24271
|
+
startedAt = performance2.now();
|
|
23919
24272
|
timer2 = setInterval(() => {
|
|
23920
|
-
|
|
24273
|
+
if (startedAt === null) return;
|
|
24274
|
+
const nextTick = animationTickAt(startedAt, performance2.now());
|
|
24275
|
+
if (nextTick === tick) return;
|
|
24276
|
+
tick = nextTick;
|
|
23921
24277
|
for (const l of listeners) l();
|
|
23922
24278
|
}, ANIMATION_TICK_MS);
|
|
23923
24279
|
timer2.unref?.();
|
|
@@ -23927,6 +24283,7 @@ function subscribe(listener) {
|
|
|
23927
24283
|
if (listeners.size === 0 && timer2) {
|
|
23928
24284
|
clearInterval(timer2);
|
|
23929
24285
|
timer2 = null;
|
|
24286
|
+
startedAt = null;
|
|
23930
24287
|
}
|
|
23931
24288
|
};
|
|
23932
24289
|
}
|
|
@@ -23935,7 +24292,7 @@ function Spinner({ type = "dots" }) {
|
|
|
23935
24292
|
const frames = FRAMES[type] ?? FRAMES.dots;
|
|
23936
24293
|
return /* @__PURE__ */ jsx8(Text7, { children: frames[t % frames.length] });
|
|
23937
24294
|
}
|
|
23938
|
-
var FRAMES, ANIMATION_TICK_MS, tick, timer2, listeners, getTick;
|
|
24295
|
+
var FRAMES, ANIMATION_TICK_MS, tick, startedAt, timer2, listeners, getTick;
|
|
23939
24296
|
var init_spinner = __esm({
|
|
23940
24297
|
"src/ui/spinner.tsx"() {
|
|
23941
24298
|
"use strict";
|
|
@@ -23946,6 +24303,7 @@ var init_spinner = __esm({
|
|
|
23946
24303
|
};
|
|
23947
24304
|
ANIMATION_TICK_MS = 100;
|
|
23948
24305
|
tick = 0;
|
|
24306
|
+
startedAt = null;
|
|
23949
24307
|
timer2 = null;
|
|
23950
24308
|
listeners = /* @__PURE__ */ new Set();
|
|
23951
24309
|
getTick = () => tick;
|
|
@@ -24644,7 +25002,7 @@ var init_tool_view = __esm({
|
|
|
24644
25002
|
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
24645
25003
|
return () => clearInterval(id);
|
|
24646
25004
|
}, [evt.status, evt.startedAt]);
|
|
24647
|
-
const statusIcon = evt.status === "queued" ? /* @__PURE__ */ jsx10(Text9, { color: theme.muted?.color ?? theme.info.color, children: "[ ]" }) : evt.status === "running" ? /* @__PURE__ */ jsx10(Text9, { color: theme.info.color, children: /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) }) : evt.status === "error" ? /* @__PURE__ */ jsx10(Text9, { color: theme.palette.error, children: "[err]" }) : evt.status === "cancelled" ? /* @__PURE__ */ jsx10(Text9, { color: theme.info.color, children: "[x]" }) : evt.status === "rejected" ? /* @__PURE__ */ jsx10(Text9, { color: theme.palette.error, children: "[!]" }) : /* @__PURE__ */ jsx10(Text9, { color: theme.palette.success, children: "[ok]" });
|
|
25005
|
+
const statusIcon = evt.status === "queued" ? /* @__PURE__ */ jsx10(Text9, { color: theme.muted?.color ?? theme.info.color, children: "[ ]" }) : evt.status === "running" ? /* @__PURE__ */ jsx10(Text9, { color: theme.info.color, children: /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) }) : evt.status === "blocked" ? /* @__PURE__ */ jsx10(Text9, { color: theme.muted?.color ?? theme.info.color, dimColor: true, children: "[skip]" }) : evt.status === "error" ? /* @__PURE__ */ jsx10(Text9, { color: theme.palette.error, children: "[err]" }) : evt.status === "cancelled" ? /* @__PURE__ */ jsx10(Text9, { color: theme.info.color, children: "[x]" }) : evt.status === "rejected" ? /* @__PURE__ */ jsx10(Text9, { color: theme.palette.error, children: "[!]" }) : /* @__PURE__ */ jsx10(Text9, { color: theme.palette.success, children: "[ok]" });
|
|
24648
25006
|
const rawTitle = evt.render?.title ?? `${evt.name}(${compactArgs(evt.args)})`;
|
|
24649
25007
|
let title = humanizeToolTitle(evt.name, rawTitle, intentTier);
|
|
24650
25008
|
if (evt.startedAt !== void 0 && (evt.status === "running" || evt.status === "done" || evt.status === "error")) {
|
|
@@ -24653,7 +25011,8 @@ var init_tool_view = __esm({
|
|
|
24653
25011
|
const statusLabel = evt.status === "rejected" ? /* @__PURE__ */ jsx10(Text9, { color: theme.palette.error, children: " rejected" }) : evt.status === "cancelled" ? /* @__PURE__ */ jsx10(Text9, { color: theme.info.color, children: " cancelled" }) : null;
|
|
24654
25012
|
const showItalic = evt.status === "queued" || evt.status === "cancelled" || evt.status === "rejected";
|
|
24655
25013
|
const expand = Boolean(evt.expanded || verbose);
|
|
24656
|
-
const
|
|
25014
|
+
const visibleResult = evt.status === "blocked" ? void 0 : evt.result;
|
|
25015
|
+
const lines = visibleResult ? visibleResult.split("\n") : [];
|
|
24657
25016
|
const showLimit = verbose ? 200 : 20;
|
|
24658
25017
|
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginLeft: 2, children: [
|
|
24659
25018
|
/* @__PURE__ */ jsxs7(Text9, { children: [
|
|
@@ -24664,7 +25023,7 @@ var init_tool_view = __esm({
|
|
|
24664
25023
|
isRepeated ? /* @__PURE__ */ jsx10(Text9, { color: theme.warn, children: " [warn] repeated" }) : null
|
|
24665
25024
|
] }),
|
|
24666
25025
|
evt.render?.diff ? /* @__PURE__ */ jsx10(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx10(DiffView, { ...evt.render.diff }) }) : null,
|
|
24667
|
-
|
|
25026
|
+
visibleResult && expand ? /* @__PURE__ */ jsxs7(
|
|
24668
25027
|
Box8,
|
|
24669
25028
|
{
|
|
24670
25029
|
marginLeft: 2,
|
|
@@ -24683,9 +25042,9 @@ var init_tool_view = __esm({
|
|
|
24683
25042
|
]
|
|
24684
25043
|
}
|
|
24685
25044
|
) : null,
|
|
24686
|
-
|
|
25045
|
+
visibleResult && !expand && evt.status !== "running" && evt.status !== "queued" && evt.status !== "cancelled" && evt.status !== "rejected" ? /* @__PURE__ */ jsxs7(Text9, { color: theme.info.color, children: [
|
|
24687
25046
|
" ",
|
|
24688
|
-
firstLine(
|
|
25047
|
+
firstLine(visibleResult)
|
|
24689
25048
|
] }) : null
|
|
24690
25049
|
] });
|
|
24691
25050
|
});
|
|
@@ -25891,7 +26250,7 @@ var init_queue_plan_picker = __esm({
|
|
|
25891
26250
|
import { useEffect as useEffect6, useRef as useRef4, useState as useState14 } from "react";
|
|
25892
26251
|
import { Box as Box18, Text as Text19 } from "ink";
|
|
25893
26252
|
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
25894
|
-
function TaskList({ tasks, startedAt, tokensDelta }) {
|
|
26253
|
+
function TaskList({ tasks, startedAt: startedAt2, tokensDelta }) {
|
|
25895
26254
|
const theme = useTheme();
|
|
25896
26255
|
const [now2, setNow] = useState14(Date.now());
|
|
25897
26256
|
const [celebrating, setCelebrating] = useState14(false);
|
|
@@ -25899,7 +26258,7 @@ function TaskList({ tasks, startedAt, tokensDelta }) {
|
|
|
25899
26258
|
const prevAllDoneRef = useRef4(false);
|
|
25900
26259
|
tasksRef.current = tasks;
|
|
25901
26260
|
useEffect6(() => {
|
|
25902
|
-
if (
|
|
26261
|
+
if (startedAt2 === null) return;
|
|
25903
26262
|
const id = setInterval(() => {
|
|
25904
26263
|
setNow(Date.now());
|
|
25905
26264
|
const current = tasksRef.current;
|
|
@@ -25908,7 +26267,7 @@ function TaskList({ tasks, startedAt, tokensDelta }) {
|
|
|
25908
26267
|
}
|
|
25909
26268
|
}, 1e3);
|
|
25910
26269
|
return () => clearInterval(id);
|
|
25911
|
-
}, [
|
|
26270
|
+
}, [startedAt2]);
|
|
25912
26271
|
useEffect6(() => {
|
|
25913
26272
|
const allDone2 = tasks.length > 0 && tasks.every((t) => t.status === "completed");
|
|
25914
26273
|
if (allDone2 && !prevAllDoneRef.current) {
|
|
@@ -25924,7 +26283,7 @@ function TaskList({ tasks, startedAt, tokensDelta }) {
|
|
|
25924
26283
|
const total = tasks.length;
|
|
25925
26284
|
const allDone = done === total;
|
|
25926
26285
|
const header = active ? active.title : allDone ? `${total} tasks done` : `${done}/${total}`;
|
|
25927
|
-
const elapsed =
|
|
26286
|
+
const elapsed = startedAt2 ? formatElapsed4(now2 - startedAt2) : null;
|
|
25928
26287
|
const headerStats = [elapsed, tokensDelta > 0 ? `\u2191 ${formatTokens3(tokensDelta)} tokens` : null].filter(Boolean).join(" \xB7 ");
|
|
25929
26288
|
const visibleTasks = tasks.slice(0, MAX_VISIBLE);
|
|
25930
26289
|
const hiddenPending = Math.max(0, tasks.length - visibleTasks.length);
|
|
@@ -31896,7 +32255,7 @@ var init_slash_commands = __esm({
|
|
|
31896
32255
|
text: `subagent policy: ${cfg?.subagentPolicy ?? "auto"}
|
|
31897
32256
|
usage: /subagents off|suggest|auto
|
|
31898
32257
|
|
|
31899
|
-
auto (default) lets the coordinator assess substantial tasks and delegate independent work; it is conditional, not a promise to spawn. suggest surfaces possible opportunities; off disables automatic suggestions. Explicit user instructions still win. Worker calls use normal permission checks and respect spend, concurrency, timeout, and read-only limits. Hotcell is the only worker backend: install the local CLI/daemon, configure its OpenRouter route, and use a clean
|
|
32258
|
+
auto (default) lets the coordinator assess substantial tasks and delegate independent work; it is conditional, not a promise to spawn. suggest surfaces possible opportunities; off disables automatic suggestions. Explicit user instructions still win. Worker calls use normal permission checks and respect spend, concurrency, timeout, and read-only limits. Hotcell is the only worker backend: install the local CLI/daemon, configure its OpenRouter route, and use a clean, pushed Git checkout; workers are read-only.`
|
|
31900
32259
|
}]);
|
|
31901
32260
|
return true;
|
|
31902
32261
|
}
|
|
@@ -35203,7 +35562,7 @@ ${wcagWarnings.join("\n")}` }
|
|
|
35203
35562
|
const onIterationEnd = useCallback8(
|
|
35204
35563
|
async (messages, signal) => {
|
|
35205
35564
|
if (signal.aborted) return messages;
|
|
35206
|
-
if (!shouldCompact({ messages })) return messages;
|
|
35565
|
+
if (!shouldCompact({ messages, tokenThreshold: cfg ? proactiveCompactionThreshold(cfg.model) : void 0 })) return messages;
|
|
35207
35566
|
if (hooksManagerRef.current.hasEnabledHooks("PreCompact")) {
|
|
35208
35567
|
void hooksManagerRef.current.fire(
|
|
35209
35568
|
"PreCompact",
|
|
@@ -36012,6 +36371,16 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36012
36371
|
const turnScope = sessionScopeRef.current.createChild();
|
|
36013
36372
|
activeScopeRef.current = turnScope;
|
|
36014
36373
|
const sharedCallbacks = {
|
|
36374
|
+
onCompacted: (info) => {
|
|
36375
|
+
setEvents((e) => [
|
|
36376
|
+
...e,
|
|
36377
|
+
{
|
|
36378
|
+
kind: "info",
|
|
36379
|
+
key: mkKey(),
|
|
36380
|
+
text: `context budget: compacted ${info.turnsRemoved} older turn${info.turnsRemoved === 1 ? "" : "s"} (${info.tokensBefore} \u2192 ${info.tokensAfter} tokens, ${info.artifactsArchived} artifacts archived)`
|
|
36381
|
+
}
|
|
36382
|
+
]);
|
|
36383
|
+
},
|
|
36015
36384
|
onAssistantStart: () => {
|
|
36016
36385
|
const id = mkAssistantId();
|
|
36017
36386
|
activeAsstIdRef.current = id;
|
|
@@ -36085,8 +36454,8 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36085
36454
|
endTurn();
|
|
36086
36455
|
}
|
|
36087
36456
|
updateTool(r.tool_call_id, {
|
|
36088
|
-
status: !r.ok && typeof r.content === "string" && r.content.startsWith("Permission denied") ? "rejected" : r.ok ? "done" : "error",
|
|
36089
|
-
result: r.content
|
|
36457
|
+
status: r.guardrail ? "blocked" : !r.ok && typeof r.content === "string" && r.content.startsWith("Permission denied") ? "rejected" : r.ok ? "done" : "error",
|
|
36458
|
+
result: r.guardrail ? void 0 : r.content
|
|
36090
36459
|
});
|
|
36091
36460
|
},
|
|
36092
36461
|
onUsage: (u) => {
|
|
@@ -36118,6 +36487,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36118
36487
|
},
|
|
36119
36488
|
askPermission: askForPermission,
|
|
36120
36489
|
onGuardrail: (ev) => {
|
|
36490
|
+
if (ev.kind === "loop_recovery" || ev.kind === "loop_stopped") return;
|
|
36121
36491
|
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: ev.message }]);
|
|
36122
36492
|
},
|
|
36123
36493
|
onKimiMdStale: () => {
|
|
@@ -36214,6 +36584,15 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36214
36584
|
allowDirectPush: cfg.allowDirectPush,
|
|
36215
36585
|
preferPullRequests: cfg.preferPullRequests,
|
|
36216
36586
|
onIterationEnd,
|
|
36587
|
+
// Request preflight archives older turns here when the next request
|
|
36588
|
+
// would not fit the model's input budget.
|
|
36589
|
+
compaction: {
|
|
36590
|
+
getState: () => sessionStateRef.current,
|
|
36591
|
+
setState: (state) => {
|
|
36592
|
+
sessionStateRef.current = state;
|
|
36593
|
+
},
|
|
36594
|
+
getStore: () => artifactStoreRef.current
|
|
36595
|
+
},
|
|
36217
36596
|
intentClassification: classification,
|
|
36218
36597
|
delegationDirective: delegationGuidance.directive,
|
|
36219
36598
|
sessionStartRecall: sessionStartRecallRef.current ?? void 0,
|
|
@@ -36244,7 +36623,7 @@ ${result2.output ?? "(output not captured)"}`,
|
|
|
36244
36623
|
return;
|
|
36245
36624
|
}
|
|
36246
36625
|
let didCompact = false;
|
|
36247
|
-
if (shouldCompact({ messages: messagesRef.current })) {
|
|
36626
|
+
if (shouldCompact({ messages: messagesRef.current, tokenThreshold: proactiveCompactionThreshold(overrideModel ?? cfg.model) })) {
|
|
36248
36627
|
if (hooksManagerRef.current.hasEnabledHooks("PreCompact")) {
|
|
36249
36628
|
void hooksManagerRef.current.fire(
|
|
36250
36629
|
"PreCompact",
|
|
@@ -37187,6 +37566,7 @@ var init_app = __esm({
|
|
|
37187
37566
|
init_system_prompt();
|
|
37188
37567
|
init_llm_summarize();
|
|
37189
37568
|
init_artifact_compaction();
|
|
37569
|
+
init_context_budget();
|
|
37190
37570
|
init_session_state();
|
|
37191
37571
|
init_executor();
|
|
37192
37572
|
init_manager5();
|
|
@@ -37381,6 +37761,7 @@ init_system_prompt();
|
|
|
37381
37761
|
init_executor();
|
|
37382
37762
|
init_errors();
|
|
37383
37763
|
init_sessions();
|
|
37764
|
+
init_context_budget();
|
|
37384
37765
|
init_image();
|
|
37385
37766
|
init_glob();
|
|
37386
37767
|
init_permissions_evaluator();
|
|
@@ -37514,13 +37895,15 @@ async function runPrintMode(opts2) {
|
|
|
37514
37895
|
if (isNew || sessionFile.messages.length === 0) {
|
|
37515
37896
|
messages.push({
|
|
37516
37897
|
role: "system",
|
|
37517
|
-
content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests
|
|
37898
|
+
content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
|
|
37518
37899
|
});
|
|
37519
37900
|
} else {
|
|
37520
|
-
const nonSystem = sessionFile.messages.filter(
|
|
37901
|
+
const nonSystem = sessionFile.messages.filter(
|
|
37902
|
+
(m) => m.role !== "system" || typeof m.content === "string" && m.content.startsWith("[compiled session state]")
|
|
37903
|
+
);
|
|
37521
37904
|
messages.push({
|
|
37522
37905
|
role: "system",
|
|
37523
|
-
content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests
|
|
37906
|
+
content: buildSystemPrompt({ cwd, tools: workerTools, model: opts2.model, preferPullRequests: opts2.preferPullRequests })
|
|
37524
37907
|
});
|
|
37525
37908
|
messages.push(...nonSystem);
|
|
37526
37909
|
}
|
|
@@ -37686,6 +38069,9 @@ async function runPrintMode(opts2) {
|
|
|
37686
38069
|
coauthor: opts2.coauthor !== false ? { name: opts2.coauthorName || "autopilot", email: opts2.coauthorEmail || "kimiflare@proton.me" } : void 0,
|
|
37687
38070
|
allowDirectPush: opts2.allowDirectPush,
|
|
37688
38071
|
preferPullRequests: opts2.preferPullRequests,
|
|
38072
|
+
// --continue sessions grow across runs; archive older turns onto the
|
|
38073
|
+
// session file (saved below) instead of failing on context size.
|
|
38074
|
+
compaction: sessionFileCompactionTarget(sessionFile),
|
|
37689
38075
|
callbacks
|
|
37690
38076
|
});
|
|
37691
38077
|
} catch (err) {
|