@dreb/coding-agent 2.46.0 → 2.48.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.
Files changed (54) hide show
  1. package/README.md +4 -2
  2. package/dist/core/agent-session.d.ts +22 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +109 -20
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/index.d.ts +1 -1
  7. package/dist/core/extensions/index.d.ts.map +1 -1
  8. package/dist/core/extensions/index.js.map +1 -1
  9. package/dist/core/extensions/runner.d.ts +7 -0
  10. package/dist/core/extensions/runner.d.ts.map +1 -1
  11. package/dist/core/extensions/runner.js +10 -0
  12. package/dist/core/extensions/runner.js.map +1 -1
  13. package/dist/core/extensions/types.d.ts +35 -0
  14. package/dist/core/extensions/types.d.ts.map +1 -1
  15. package/dist/core/extensions/types.js.map +1 -1
  16. package/dist/core/k3-context-tier.d.ts +67 -0
  17. package/dist/core/k3-context-tier.d.ts.map +1 -0
  18. package/dist/core/k3-context-tier.js +76 -0
  19. package/dist/core/k3-context-tier.js.map +1 -0
  20. package/dist/core/sdk.d.ts.map +1 -1
  21. package/dist/core/sdk.js +6 -1
  22. package/dist/core/sdk.js.map +1 -1
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +38 -2
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/core/tools/ask-user.d.ts +45 -0
  27. package/dist/core/tools/ask-user.d.ts.map +1 -0
  28. package/dist/core/tools/ask-user.js +210 -0
  29. package/dist/core/tools/ask-user.js.map +1 -0
  30. package/dist/core/tools/index.d.ts +11 -0
  31. package/dist/core/tools/index.d.ts.map +1 -1
  32. package/dist/core/tools/index.js +8 -0
  33. package/dist/core/tools/index.js.map +1 -1
  34. package/dist/modes/interactive/components/ask-user.d.ts +59 -0
  35. package/dist/modes/interactive/components/ask-user.d.ts.map +1 -0
  36. package/dist/modes/interactive/components/ask-user.js +240 -0
  37. package/dist/modes/interactive/components/ask-user.js.map +1 -0
  38. package/dist/modes/interactive/interactive-mode.d.ts +12 -0
  39. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  40. package/dist/modes/interactive/interactive-mode.js +99 -5
  41. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  42. package/dist/modes/rpc/rpc-mode.d.ts +16 -1
  43. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  44. package/dist/modes/rpc/rpc-mode.js +112 -59
  45. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  46. package/dist/modes/rpc/rpc-types.d.ts +24 -0
  47. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  48. package/dist/modes/rpc/rpc-types.js.map +1 -1
  49. package/docs/dashboard.md +1 -1
  50. package/docs/extensions.md +33 -3
  51. package/docs/providers.md +1 -0
  52. package/docs/rpc.md +46 -6
  53. package/docs/session.md +1 -1
  54. package/package.json +1 -1
@@ -29,6 +29,7 @@ import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
29
29
  import { checkScriptContent, extractScriptPaths, isForbiddenCommand } from "./forbidden-commands.js";
30
30
  import { getGitRepoState, getGitStatusMetadata } from "./git-repo-state.js";
31
31
  import { findGitRoot } from "./git-root.js";
32
+ import { deriveK3ContextTierModel, isK3256kTier, K3_1M_CONTEXT_WINDOW, K3_256K_CONTEXT_WINDOW, K3_UPGRADE_CUTOFF_TOKENS, shouldUpgradeK3Tier, } from "./k3-context-tier.js";
32
33
  import { log } from "./logger.js";
33
34
  import { computeNestedContextBlock } from "./nested-context.js";
34
35
  import { PerformanceTracker } from "./performance-tracker.js";
@@ -883,7 +884,12 @@ export class AgentSession {
883
884
  }
884
885
  /** Emit extension events based on agent events */
885
886
  async _emitExtensionEvent(event) {
886
- if (!this._extensionRunner)
887
+ // The runner is created unconditionally (so built-in tools like ask_user
888
+ // always have a UI context). When no extensions are loaded there are no
889
+ // handlers to invoke — return synchronously to avoid inserting an extra
890
+ // await tick per event, which would otherwise delay the final agent_end
891
+ // emission past when prompt() resolves.
892
+ if (!this._extensionRunner || !this._extensionRunner.hasExtensions)
887
893
  return;
888
894
  if (event.type === "agent_start") {
889
895
  this._turnIndex = 0;
@@ -1692,7 +1698,7 @@ export class AgentSession {
1692
1698
  }
1693
1699
  const previousModel = this.model;
1694
1700
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1695
- this.agent.setModel(model);
1701
+ this.agent.setModel(this._applyContextTier(model));
1696
1702
  this._refreshThinkingDisplay(model);
1697
1703
  this.sessionManager.appendModelChange(model.provider, model.id);
1698
1704
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
@@ -1755,7 +1761,7 @@ export class AgentSession {
1755
1761
  const next = scopedModels[nextIndex];
1756
1762
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1757
1763
  // Apply model
1758
- this.agent.setModel(next.model);
1764
+ this.agent.setModel(this._applyContextTier(next.model));
1759
1765
  this._refreshThinkingDisplay(next.model);
1760
1766
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1761
1767
  this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
@@ -1785,7 +1791,7 @@ export class AgentSession {
1785
1791
  throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1786
1792
  }
1787
1793
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1788
- this.agent.setModel(nextModel);
1794
+ this.agent.setModel(this._applyContextTier(nextModel));
1789
1795
  this._refreshThinkingDisplay(nextModel);
1790
1796
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1791
1797
  this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
@@ -1974,6 +1980,11 @@ export class AgentSession {
1974
1980
  const newEntries = this.sessionManager.getEntries();
1975
1981
  const sessionContext = this.sessionManager.buildSessionContext();
1976
1982
  this.agent.replaceMessages(sessionContext.messages);
1983
+ // Re-derive the K3 context tier: the compacted context is small again,
1984
+ // so the session returns to the cheaper 256k wire tier.
1985
+ if (this.model) {
1986
+ this.agent.setModel(this._applyContextTier(this.model));
1987
+ }
1977
1988
  // Get the saved compaction entry for the extension event
1978
1989
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1979
1990
  if (this._extensionRunner && savedCompactionEntry) {
@@ -2021,12 +2032,10 @@ export class AgentSession {
2021
2032
  */
2022
2033
  async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
2023
2034
  const settings = this.settingsManager.getCompactionSettings();
2024
- if (!settings.enabled)
2025
- return;
2026
2035
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
2027
2036
  if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
2028
2037
  return;
2029
- const contextWindow = this.model?.contextWindow ?? 0;
2038
+ let contextWindow = this.model?.contextWindow ?? 0;
2030
2039
  // Skip overflow check if the message came from a different model.
2031
2040
  // This handles the case where user switched from a smaller-context model (e.g. opus)
2032
2041
  // to a larger-context model (e.g. codex) - the overflow error from the old model
@@ -2042,6 +2051,23 @@ export class AgentSession {
2042
2051
  }
2043
2052
  // Case 1: Overflow - LLM returned context overflow error
2044
2053
  if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
2054
+ // K3 auto context tier: an overflow in the 256k tier upgrades to the
2055
+ // 1M tier instead of compacting — the Kimi backend grows the prompt
2056
+ // cache seamlessly. This runs even when compaction is disabled since
2057
+ // no context reduction is involved.
2058
+ if (this._tryUpgradeK3ContextTier()) {
2059
+ // Remove the error message from agent state (it IS saved to session
2060
+ // for history, but we don't want it in context for the retry)
2061
+ this._removeLastAssistantMessage();
2062
+ setTimeout(() => {
2063
+ this.agent.continue().catch((err) => {
2064
+ this.warnInSession(`Agent failed to continue after context window upgrade: ${err instanceof Error ? err.message : String(err)}`);
2065
+ });
2066
+ }, 100);
2067
+ return;
2068
+ }
2069
+ if (!settings.enabled)
2070
+ return;
2045
2071
  if (this._overflowRecoveryAttempted) {
2046
2072
  this._emit({
2047
2073
  type: "auto_compaction_end",
@@ -2055,10 +2081,7 @@ export class AgentSession {
2055
2081
  this._overflowRecoveryAttempted = true;
2056
2082
  // Remove the error message from agent state (it IS saved to session for history,
2057
2083
  // but we don't want it in context for the retry)
2058
- const messages = this.agent.state.messages;
2059
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2060
- this.agent.replaceMessages(messages.slice(0, -1));
2061
- }
2084
+ this._removeLastAssistantMessage();
2062
2085
  await this._runAutoCompaction("overflow", true);
2063
2086
  return;
2064
2087
  }
@@ -2085,10 +2108,65 @@ export class AgentSession {
2085
2108
  else {
2086
2109
  contextTokens = calculateContextTokens(assistantMessage.usage);
2087
2110
  }
2111
+ // K3 auto context tier: reaching the 256k cutoff upgrades to the 1M tier
2112
+ // instead of compacting. This is model-capability management, not context
2113
+ // reduction, so it applies even when compaction is disabled. The cutoff is
2114
+ // fixed at the default compaction threshold of the 256k window; a
2115
+ // user-lowered compaction threshold takes precedence and effectively
2116
+ // disables the upgrade.
2117
+ if (shouldUpgradeK3Tier(this.model, contextTokens)) {
2118
+ // A user-lowered compaction threshold takes precedence over the
2119
+ // upgrade: if the user's compact point for the 256k window sits below
2120
+ // the default cutoff and is already exceeded, compact instead.
2121
+ const userCompactPoint = K3_256K_CONTEXT_WINDOW - settings.reserveTokens;
2122
+ const userThresholdPreempts = settings.enabled && userCompactPoint < K3_UPGRADE_CUTOFF_TOKENS && contextTokens > userCompactPoint;
2123
+ if (!userThresholdPreempts) {
2124
+ this._tryUpgradeK3ContextTier();
2125
+ contextWindow = this.model?.contextWindow ?? contextWindow;
2126
+ }
2127
+ }
2128
+ if (!settings.enabled)
2129
+ return;
2088
2130
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2089
2131
  await this._runAutoCompaction("threshold", false);
2090
2132
  }
2091
2133
  }
2134
+ /**
2135
+ * Apply the K3 auto context tier to a model being set on the agent. The
2136
+ * user-facing `k3` model runs on the cheaper `k3-256k` wire model ID until
2137
+ * the session context grows past the 256k cutoff; no-op for other models.
2138
+ * See k3-context-tier.ts.
2139
+ */
2140
+ _applyContextTier(model) {
2141
+ return deriveK3ContextTierModel(model, estimateContextTokens(this.agent.state.messages).tokens);
2142
+ }
2143
+ /** Remove the last message from agent state when it is an assistant message. */
2144
+ _removeLastAssistantMessage() {
2145
+ const messages = this.agent.state.messages;
2146
+ if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2147
+ this.agent.replaceMessages(messages.slice(0, -1));
2148
+ }
2149
+ }
2150
+ /**
2151
+ * Upgrade the K3 auto context tier from 256k to 1M. The Kimi backend
2152
+ * upgrades the prompt cache seamlessly, so no context is lost or
2153
+ * compacted. Returns true when the upgrade was applied.
2154
+ */
2155
+ _tryUpgradeK3ContextTier() {
2156
+ const model = this.model;
2157
+ if (!model || !isK3256kTier(model))
2158
+ return false;
2159
+ const upgraded = deriveK3ContextTierModel(model, K3_UPGRADE_CUTOFF_TOKENS + 1);
2160
+ this.agent.setModel(upgraded);
2161
+ this._emit({
2162
+ type: "context_window_upgrade",
2163
+ provider: upgraded.provider,
2164
+ modelId: upgraded.id,
2165
+ fromContextWindow: K3_256K_CONTEXT_WINDOW,
2166
+ toContextWindow: K3_1M_CONTEXT_WINDOW,
2167
+ });
2168
+ return true;
2169
+ }
2092
2170
  /**
2093
2171
  * Internal: Run auto-compaction with events.
2094
2172
  */
@@ -2158,6 +2236,11 @@ export class AgentSession {
2158
2236
  const newEntries = this.sessionManager.getEntries();
2159
2237
  const sessionContext = this.sessionManager.buildSessionContext();
2160
2238
  this.agent.replaceMessages(sessionContext.messages);
2239
+ // Re-derive the K3 context tier: the compacted context is small again,
2240
+ // so the session returns to the cheaper 256k wire tier.
2241
+ if (this.model) {
2242
+ this.agent.setModel(this._applyContextTier(this.model));
2243
+ }
2161
2244
  // Get the saved compaction entry for the extension event
2162
2245
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2163
2246
  if (this._extensionRunner && savedCompactionEntry) {
@@ -2300,7 +2383,7 @@ export class AgentSession {
2300
2383
  if (!refreshedModel || refreshedModel === currentModel) {
2301
2384
  return;
2302
2385
  }
2303
- this.agent.setModel(refreshedModel);
2386
+ this.agent.setModel(this._applyContextTier(refreshedModel));
2304
2387
  this._refreshThinkingDisplay(refreshedModel);
2305
2388
  }
2306
2389
  _bindExtensionCore(runner) {
@@ -2443,9 +2526,15 @@ export class AgentSession {
2443
2526
  const wrappedExtensionTools = this._extensionRunner
2444
2527
  ? wrapRegisteredTools(allCustomTools, this._extensionRunner)
2445
2528
  : [];
2529
+ // Give base tools a per-execution extension context so built-ins like
2530
+ // ask_user can reach ctx.ui. createContext() snapshots hasUI at call time,
2531
+ // so print/RPC-without-host modes degrade to the no-op UI (hasUI === false).
2532
+ const baseToolCtxFactory = this._extensionRunner
2533
+ ? () => this._extensionRunner.createContext()
2534
+ : undefined;
2446
2535
  const toolRegistry = new Map(Array.from(this._baseToolDefinitions.values()).map((definition) => [
2447
2536
  definition.name,
2448
- wrapToolDefinition(definition),
2537
+ wrapToolDefinition(definition, baseToolCtxFactory),
2449
2538
  ]));
2450
2539
  for (const tool of wrappedExtensionTools) {
2451
2540
  toolRegistry.set(tool.name, tool);
@@ -2544,12 +2633,11 @@ export class AgentSession {
2544
2633
  extensionsResult.runtime.flagValues.set(name, value);
2545
2634
  }
2546
2635
  }
2547
- const hasExtensions = extensionsResult.extensions.length > 0;
2548
- const hasCustomTools = this._customTools.length > 0;
2549
- this._extensionRunner =
2550
- hasExtensions || hasCustomTools
2551
- ? new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry)
2552
- : undefined;
2636
+ // The runner also owns the cross-surface UI context used by built-in
2637
+ // tools such as ask_user. Create it even when no third-party extensions
2638
+ // are loaded; otherwise ordinary TUI/Dashboard sessions give base tools
2639
+ // no ctx.ui and ask_user can never open its dialog.
2640
+ this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry);
2553
2641
  if (this._extensionRunnerRef) {
2554
2642
  this._extensionRunnerRef.current = this._extensionRunner;
2555
2643
  }
@@ -2572,6 +2660,7 @@ export class AgentSession {
2572
2660
  "subagent",
2573
2661
  "wait",
2574
2662
  "search",
2663
+ "ask_user",
2575
2664
  "skill",
2576
2665
  "tasks_update",
2577
2666
  "suggest_next",
@@ -2864,7 +2953,7 @@ export class AgentSession {
2864
2953
  const availableModels = await this._modelRegistry.getAvailable();
2865
2954
  const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2866
2955
  if (match) {
2867
- this.agent.setModel(match);
2956
+ this.agent.setModel(this._applyContextTier(match));
2868
2957
  this._refreshThinkingDisplay(match);
2869
2958
  await this._emitModelSelect(match, previousModel, "restore");
2870
2959
  }