@kenkaiiii/ggcoder 5.82.0 → 5.83.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 (69) hide show
  1. package/README.md +1 -1
  2. package/dist/app-sidecar/app-settings.js +11 -5
  3. package/dist/app-sidecar/app-settings.js.map +1 -1
  4. package/dist/app-sidecar/auth-routes.js +8 -3
  5. package/dist/app-sidecar/auth-routes.js.map +1 -1
  6. package/dist/app-sidecar/goal-routes.js +7 -0
  7. package/dist/app-sidecar/goal-routes.js.map +1 -1
  8. package/dist/app-sidecar/run-control-routes.js +31 -3
  9. package/dist/app-sidecar/run-control-routes.js.map +1 -1
  10. package/dist/app-sidecar.js +65 -11
  11. package/dist/app-sidecar.js.map +1 -1
  12. package/dist/cli/auth.js +22 -1
  13. package/dist/cli/auth.js.map +1 -1
  14. package/dist/cli.js +7 -7
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config.js +4 -2
  17. package/dist/config.js.map +1 -1
  18. package/dist/core/agent-notifications.d.ts +18 -0
  19. package/dist/core/agent-notifications.js +35 -2
  20. package/dist/core/agent-notifications.js.map +1 -1
  21. package/dist/core/agent-session.d.ts +71 -1
  22. package/dist/core/agent-session.js +175 -16
  23. package/dist/core/agent-session.js.map +1 -1
  24. package/dist/core/compaction/policy.d.ts +2 -2
  25. package/dist/core/compaction/policy.js +86 -43
  26. package/dist/core/compaction/policy.js.map +1 -1
  27. package/dist/core/compaction/token-estimator.d.ts +4 -1
  28. package/dist/core/compaction/token-estimator.js +5 -2
  29. package/dist/core/compaction/token-estimator.js.map +1 -1
  30. package/dist/core/idle-wake.d.ts +62 -0
  31. package/dist/core/idle-wake.js +103 -0
  32. package/dist/core/idle-wake.js.map +1 -0
  33. package/dist/core/process-manager.d.ts +51 -11
  34. package/dist/core/process-manager.js +229 -54
  35. package/dist/core/process-manager.js.map +1 -1
  36. package/dist/core/slash-commands.d.ts +5 -1
  37. package/dist/core/slash-commands.js +24 -9
  38. package/dist/core/slash-commands.js.map +1 -1
  39. package/dist/core/steering.d.ts +7 -0
  40. package/dist/core/steering.js +13 -0
  41. package/dist/core/steering.js.map +1 -1
  42. package/dist/core/subagent-manager.js +5 -4
  43. package/dist/core/subagent-manager.js.map +1 -1
  44. package/dist/goals-agent/goals-agent.js +3 -2
  45. package/dist/goals-agent/goals-agent.js.map +1 -1
  46. package/dist/interactive.js +9 -3
  47. package/dist/interactive.js.map +1 -1
  48. package/dist/modes/acp-mode.js +8 -1
  49. package/dist/modes/acp-mode.js.map +1 -1
  50. package/dist/modes/agent-home-mode.js +13 -1
  51. package/dist/modes/agent-home-mode.js.map +1 -1
  52. package/dist/modes/rpc-mode.d.ts +2 -1
  53. package/dist/modes/rpc-mode.js +15 -3
  54. package/dist/modes/rpc-mode.js.map +1 -1
  55. package/dist/modes/serve-mode.js +13 -1
  56. package/dist/modes/serve-mode.js.map +1 -1
  57. package/dist/tools/background-watch-lint.d.ts +18 -0
  58. package/dist/tools/background-watch-lint.js +102 -0
  59. package/dist/tools/background-watch-lint.js.map +1 -0
  60. package/dist/tools/bash.js +5 -2
  61. package/dist/tools/bash.js.map +1 -1
  62. package/dist/tools/subagent-shared.js +4 -1
  63. package/dist/tools/subagent-shared.js.map +1 -1
  64. package/dist/tools/task-output.js +11 -1
  65. package/dist/tools/task-output.js.map +1 -1
  66. package/dist/ui/components/ModelSelector.d.ts +10 -0
  67. package/dist/ui/components/ModelSelector.js +51 -5
  68. package/dist/ui/components/ModelSelector.js.map +1 -1
  69. package/package.json +4 -4
@@ -1,63 +1,106 @@
1
1
  export const DEFAULT_COMPACTION_THRESHOLD = 0.85;
2
2
  /**
3
- * Latency caps on the context window used for compaction TRIGGERING.
3
+ * Caps on the context window used for compaction TRIGGERING, per provider.
4
4
  *
5
- * Some providers advertise huge windows (GLM: 1M, MiniMax/MiMo/Gemini: 1M+)
6
- * whose pricing model tolerates but whose transports may not: on the public
7
- * API every turn re-prefills the whole active context, and observed GLM
8
- * prefill was ~3-5K tok/s uncached (2026-09-22 sidecar log: 44-68s
9
- * time-to-first-token at 213K tokens). A session sitting at 21% of a 1M
10
- * window therefore paid a 45-70s tax on EVERY model call without ever
11
- * approaching the 85% compaction trigger.
5
+ * The trigger is `threshold × min(contextWindow, cap)`. A cap moves only when
6
+ * compaction summarizes; the model's true window still governs hard limits
7
+ * (request size, output budgets), and min() keeps a smaller advertised window
8
+ * authoritative — a cap never raises a trigger.
12
9
  *
13
- * Three tiers, ALL evidence-driven — this is deliberately not GLM-only:
10
+ * Every entry below needs evidence of ONE of these, cited inline:
14
11
  *
15
- * 1. MEASURED SLOW — explicit cap (glm: 176K → trigger ~150K, from the
16
- * incident log).
17
- * 2. MEASURED HEALTHY — no cap: the transport's prompt caching demonstrably
18
- * keeps large contexts fast (anthropic: we send explicit cache_control
19
- * breakpoints; openai: sidecar audit shows flat 5-6s TTFT p95 at 237K
20
- * tokens, zero stalls in 2,000+ turns). Capping these would throw away
21
- * working context for nothing.
22
- * 3. UNKNOWN — the conservative default cap below. Any provider we have
23
- * not measured gets it, so a user on ANY model is protected from the
24
- * mega-window + cold-prefill pattern. The `cache_health` /
25
- * `prompt_cache_miss` diag lines (agent-loop) turn tier 3 into tier 1 or
26
- * 2 with one line here as evidence arrives — remove a provider's cap
27
- * once its logs show healthy cache reads at large sizes, lower it when
28
- * they show misses.
12
+ * - a hard input limit below the advertised window;
13
+ * - measured slow turns (re-prefill without prompt caching);
14
+ * - a price tier that reprices the WHOLE request once the prompt crosses a
15
+ * size, so every turn past it costs more;
16
+ * - the vendor's own agent compacting this model earlier on the same route.
29
17
  *
30
- * The model's true context window still governs hard limits (request size,
31
- * output budgets); a cap only moves when compaction summarizes. min() keeps a
32
- * smaller advertised window authoritative — the default never raises a
33
- * trigger that the provider's real window already keeps lower.
18
+ * "uncapped" needs evidence too: automatic prompt caching with cheap cache
19
+ * hits and no long-context tier, or measured fast turns at large sizes.
20
+ * Providers with no evidence either way keep {@link DEFAULT_LATENCY_WINDOW_CAP}.
21
+ * Verified 2026-10-16 against the vendors' agents in source (deepseek-harness,
22
+ * gemini-cli, qwen-code, codex, CodeWhale's model/pricing tables) and docs.
34
23
  */
35
- const PROVIDER_LATENCY_WINDOW_CAPS = {
24
+ const PROVIDER_WINDOW_CAPS = {
25
+ // Measured: TTFT 1.1s <50K → 3.6s at 700K–1M, ~100% cache reads over
26
+ // 26,000+ turns (session logs, 2026-10). No long-context price tier. Claude
27
+ anthropic: "uncapped",
28
+ // Price line: once input passes 272K, OpenAI bills the WHOLE request at 2×
29
+ // input/cache and 1.5× output (OpenAI model pages, via CodeWhale pricing.rs
30
+ // OPENAI_LONG_CONTEXT_SURCHARGE_THRESHOLD). Codex itself stays under it,
31
+ // compacting at 90% of a 272K window (codex-rs/protocol/src/openai_models.rs
32
+ // auto_compact_token_limit). Trigger 231K on both routes: the API key's
33
+ // 1.05M window is capped here, the Codex OAuth route already passes 272K.
34
+ openai: 272_000,
35
+ // Measured slow: every turn re-prefilled the whole context on the Coding
36
+ // Plan endpoint at ~3–5K tok/s (2026-09-22 sidecar log: 44–68s TTFT at 213K).
36
37
  glm: 176_000,
38
+ // DeepSeek's own agent compacts at 80% of a 1M window with no cap
39
+ // (deepseek-harness packages/compaction: ratio 0.8, DEFAULT_CONTEXT_WINDOW
40
+ // 1_000_000). Automatic prefix caching, hits ~1/50 of a miss (pi models:
41
+ // cacheRead 0.006 vs input 0.30), no long-context surcharge.
42
+ deepseek: "uncapped",
43
+ // Kimi K3: automatic caching, hit $0.30 vs miss $3.00, no long-context tier
44
+ // (platform.kimi.ai pricing, via CodeWhale pricing.rs). Kimi For Coding
45
+ // (K2.8 Preview) serves up to 1M (kimi.com/code/docs).
46
+ moonshot: "uncapped",
47
+ // Measured: mimo-v2.6-pro ~1,600 turns, TTFT 4–7s, 95–100% of input read
48
+ // from cache up to 250K (session logs). Cache hit $0.0036 vs miss $0.435
49
+ // (~1/120), and V2.6 has a single price tier (Xiaomi rate card).
50
+ xiaomi: "uncapped",
51
+ // MiniMax-M3 doubles its price once input passes 512K (0.30 → 0.60 in, cache
52
+ // 0.06 → 0.12: CodeWhale pricing.rs MINIMAX_M3_LONG_CONTEXT_THRESHOLD, from
53
+ // MiniMax's pay-as-you-go page), and 512K is the guaranteed window; "1M" is
54
+ // up to. Trigger 435K stays under the line.
55
+ minimax: 512_000,
56
+ // Google's own Gemini CLI compresses at 50% of the window on this same
57
+ // Code Assist route (packages/cli/src/ui/constants.ts
58
+ // DEFAULT_COMPRESSION_THRESHOLD = 0.5; tokenLimit 1_048_576 for every
59
+ // Gemini model), and implicit caching on Code Assist is unverified. 616K ×
60
+ // 0.85 = 524K ≈ 50% of 1,048,576. Flash and Flash Lite are flat-priced at any
61
+ // prompt size; 3.1 Pro has its own price-line cap in MODEL_WINDOW_CAPS.
62
+ gemini: 616_000,
63
+ // Price line: xAI bills the WHOLE request at 2× ($4 / $1 cached / $12) once a
64
+ // grok-4.7 prompt reaches 200K tokens (docs.x.ai/developers/pricing;
65
+ // CodeWhale pricing.rs GROK_4_6_LONG_CONTEXT_THRESHOLD = 200_000). Trigger
66
+ // 170K stays under the line.
67
+ xai: 200_000,
68
+ // Qwen's own agent (qwen-code) runs qwen3.8-max at a 1M window and compacts
69
+ // at 85% (chatCompressionService DEFAULT_PCT). OpenRouter lists cache reads
70
+ // at $0.25 vs $2.00 input and no long-context tier.
71
+ openrouter: "uncapped",
37
72
  };
38
- /** Providers with MEASURED healthy prompt caching — exempt from the default cap. */
39
- const PROVIDERS_WITH_HEALTHY_CACHING = new Set([
40
- "anthropic", // explicit cache_control breakpoints in our transport
41
- "openai", // sidecar audit 2026-09-22: flat TTFT p95 at 237K tokens, 0 stalls / 2000+ turns
42
- ]);
43
- /** Conservative ceiling for providers whose cache behaviour is UNMEASURED.
44
- * Only bites mega-windows (≥ ~470K): typical 200-272K windows are unaffected. */
73
+ /**
74
+ * Per-model caps, checked before the provider's rule: a model whose pricing
75
+ * differs from the rest of its provider.
76
+ */
77
+ const MODEL_WINDOW_CAPS = {
78
+ // Price line: Gemini 3.1 Pro bills the WHOLE request at $4 / $18 instead of
79
+ // $2 / $12 once the prompt passes 200K tokens (Gemini API pricing); the
80
+ // Flash models stay flat. On a Code Assist licence this compacts earlier than
81
+ // Gemini CLI's 50%, which keeps API-key users off the higher rate.
82
+ "gemini-3.1-pro-preview": 200_000,
83
+ };
84
+ /** Conservative ceiling for providers with NO evidence either way (Sakana, the
85
+ * Hugging Face router — whose served window and caching vary by backend).
86
+ * Only bites windows above ~470K; 200–272K windows are unaffected. */
45
87
  const DEFAULT_LATENCY_WINDOW_CAP = 400_000;
46
- /** The latency-capped effective context window for a provider, if one applies. */
47
- export function resolveLatencyWindowCap(provider) {
48
- const explicit = PROVIDER_LATENCY_WINDOW_CAPS[provider];
49
- if (explicit != null)
50
- return explicit;
51
- if (PROVIDERS_WITH_HEALTHY_CACHING.has(provider))
88
+ /** The effective trigger window cap for a provider (and model), if one applies. */
89
+ export function resolveLatencyWindowCap(provider, model) {
90
+ const modelCap = model === undefined ? undefined : MODEL_WINDOW_CAPS[model];
91
+ if (modelCap !== undefined)
92
+ return modelCap;
93
+ const rule = PROVIDER_WINDOW_CAPS[provider];
94
+ if (rule === "uncapped")
52
95
  return undefined;
53
- return DEFAULT_LATENCY_WINDOW_CAP;
96
+ return rule ?? DEFAULT_LATENCY_WINDOW_CAP;
54
97
  }
55
98
  /** One trigger policy shared by app sessions and CLI compaction. */
56
99
  export function resolveCompactionPolicy(options) {
57
100
  const threshold = Number.isFinite(options.threshold) && options.threshold > 0 && options.threshold < 1
58
101
  ? options.threshold
59
102
  : DEFAULT_COMPACTION_THRESHOLD;
60
- const latencyCap = resolveLatencyWindowCap(options.provider);
103
+ const latencyCap = resolveLatencyWindowCap(options.provider, options.model);
61
104
  // The trigger fires against the latency-capped window when one applies —
62
105
  // min() keeps a provider-declared window smaller than the cap authoritative.
63
106
  const effectiveWindow = latencyCap
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/compaction/policy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,4BAA4B,GAAsC;IACtE,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,oFAAoF;AACpF,MAAM,8BAA8B,GAA0B,IAAI,GAAG,CAAC;IACpE,WAAW,EAAE,sDAAsD;IACnE,QAAQ,EAAE,iFAAiF;CAC9E,CAAC,CAAC;AAEjB;kFACkF;AAClF,MAAM,0BAA0B,GAAG,OAAO,CAAC;AAE3C,kFAAkF;AAClF,MAAM,UAAU,uBAAuB,CAAC,QAAkB;IACxD,MAAM,QAAQ,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,QAAQ,CAAC;IACtC,IAAI,8BAA8B,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACnE,OAAO,0BAA0B,CAAC;AACpC,CAAC;AAQD,oEAAoE;AACpE,MAAM,UAAU,uBAAuB,CAAC,OAOvC;IACC,MAAM,SAAS,GACb,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAU,GAAG,CAAC,IAAI,OAAO,CAAC,SAAU,GAAG,CAAC;QACpF,CAAC,CAAC,OAAO,CAAC,SAAU;QACpB,CAAC,CAAC,4BAA4B,CAAC;IACnC,MAAM,UAAU,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7D,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,eAAe,GAAG,UAAU;QAChC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC;QAC7C,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC;IACzE,MAAM,SAAS,GACb,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC;IACpF,OAAO;QACL,SAAS;QACT,YAAY;QACZ,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC;YACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS;YACT,aAAa,EAAE,eAAe;YAC9B,SAAS;YACT,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,IAAI;SACnD,CAAC;KACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/compaction/policy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,oBAAoB,GAAmD;IAC3E,qEAAqE;IACrE,4EAA4E;IAC5E,SAAS,EAAE,UAAU;IACrB,2EAA2E;IAC3E,4EAA4E;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,EAAE,OAAO;IACf,yEAAyE;IACzE,8EAA8E;IAC9E,GAAG,EAAE,OAAO;IACZ,kEAAkE;IAClE,2EAA2E;IAC3E,yEAAyE;IACzE,6DAA6D;IAC7D,QAAQ,EAAE,UAAU;IACpB,4EAA4E;IAC5E,wEAAwE;IACxE,uDAAuD;IACvD,QAAQ,EAAE,UAAU;IACpB,yEAAyE;IACzE,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,EAAE,UAAU;IAClB,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,4CAA4C;IAC5C,OAAO,EAAE,OAAO;IAChB,uEAAuE;IACvE,sDAAsD;IACtD,sEAAsE;IACtE,2EAA2E;IAC3E,8EAA8E;IAC9E,wEAAwE;IACxE,MAAM,EAAE,OAAO;IACf,8EAA8E;IAC9E,qEAAqE;IACrE,2EAA2E;IAC3E,6BAA6B;IAC7B,GAAG,EAAE,OAAO;IACZ,4EAA4E;IAC5E,4EAA4E;IAC5E,oDAAoD;IACpD,UAAU,EAAE,UAAU;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,iBAAiB,GAAqC;IAC1D,4EAA4E;IAC5E,wEAAwE;IACxE,8EAA8E;IAC9E,mEAAmE;IACnE,wBAAwB,EAAE,OAAO;CAClC,CAAC;AAEF;;uEAEuE;AACvE,MAAM,0BAA0B,GAAG,OAAO,CAAC;AAE3C,mFAAmF;AACnF,MAAM,UAAU,uBAAuB,CAAC,QAAkB,EAAE,KAAc;IACxE,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC5E,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,IAAI,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,IAAI,IAAI,0BAA0B,CAAC;AAC5C,CAAC;AAQD,oEAAoE;AACpE,MAAM,UAAU,uBAAuB,CAAC,OAOvC;IACC,MAAM,SAAS,GACb,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAU,GAAG,CAAC,IAAI,OAAO,CAAC,SAAU,GAAG,CAAC;QACpF,CAAC,CAAC,OAAO,CAAC,SAAU;QACpB,CAAC,CAAC,4BAA4B,CAAC;IACnC,MAAM,UAAU,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,eAAe,GAAG,UAAU;QAChC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC;QAC7C,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC;IACzE,MAAM,SAAS,GACb,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC;IACpF,OAAO;QACL,SAAS;QACT,YAAY;QACZ,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC;YACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS;YACT,aAAa,EAAE,eAAe;YAC9B,SAAS;YACT,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,IAAI;SACnD,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -27,7 +27,10 @@ export declare function measureConversationChars(messages: Message[]): {
27
27
  * its authoritative total input tokens (uncached + cache read/write). Skips
28
28
  * histories containing image/video parts.
29
29
  */
30
- export declare function calibrateEstimatorFromUsage(history: Message[], usage: Usage): void;
30
+ export declare function calibrateEstimatorFromUsage(history: Message[], usage: Usage,
31
+ /** Characters the provider also counted but `history` lacks — the tool
32
+ * definitions sent with the request. Omitting them biases the ratio low. */
33
+ extraChars?: number): void;
31
34
  export declare function estimateTokens(text: string): number;
32
35
  export declare function estimateMessageTokens(message: Message): number;
33
36
  export declare function estimateConversationTokens(messages: Message[]): number;
@@ -108,12 +108,15 @@ export function measureConversationChars(messages) {
108
108
  * its authoritative total input tokens (uncached + cache read/write). Skips
109
109
  * histories containing image/video parts.
110
110
  */
111
- export function calibrateEstimatorFromUsage(history, usage) {
111
+ export function calibrateEstimatorFromUsage(history, usage,
112
+ /** Characters the provider also counted but `history` lacks — the tool
113
+ * definitions sent with the request. Omitting them biases the ratio low. */
114
+ extraChars = 0) {
112
115
  const { chars, hasMedia } = measureConversationChars(history);
113
116
  if (hasMedia)
114
117
  return;
115
118
  const totalInputTokens = usage.inputTokens + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
116
- calibrateEstimator(chars, totalInputTokens);
119
+ calibrateEstimator(chars + Math.max(0, extraChars), totalInputTokens);
117
120
  }
118
121
  function getModelCharsPerToken() {
119
122
  if (!activeModel)
@@ -1 +1 @@
1
- {"version":3,"file":"token-estimator.js","sourceRoot":"","sources":["../../../src/core/compaction/token-estimator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-estimator.js","sourceRoot":"","sources":["../../../src/core/compaction/token-estimator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Wake an idle session when its own background work needs attention.
3
+ *
4
+ * Background alerts — a build or CI watcher that failed, a task that exited
5
+ * non-zero, a child agent that finished — ride the steering path, which only
6
+ * exists while a run is live. Once the agent has ended its turn, an alert sat
7
+ * in the queue until the user happened to send another message (measured: 8 of
8
+ * 483 runs began with a status update that had been waiting for exactly that).
9
+ *
10
+ * This starts a run for the alert instead, inside hard limits:
11
+ *
12
+ * - Never while anything else could deliver it: a live run, a run being
13
+ * claimed, an autopilot cycle. The alert waits for that run's next steering
14
+ * boundary instead, and is retried here if the run ends without draining it.
15
+ * - Never while the user is deciding something (a plan awaiting review).
16
+ * - Never after the user pressed Stop, until they send another message. Stop
17
+ * means "stop acting on your own", and a background exit is not a reason to
18
+ * override it.
19
+ * - At most {@link DEFAULT_MAX_WAKES_PER_PROMPT} wakes per user message, so a
20
+ * flapping task cannot keep the agent (and the bill) running unattended.
21
+ *
22
+ * Pure policy over injected callbacks; the host owns the run itself.
23
+ */
24
+ /** Batch alerts that land together (a failure line, then the exit) into one run. */
25
+ export declare const DEFAULT_IDLE_WAKE_DEBOUNCE_MS = 2000;
26
+ /** Unattended wakes allowed per human prompt. */
27
+ export declare const DEFAULT_MAX_WAKES_PER_PROMPT = 2;
28
+ export type IdleWakeSkip = "stopped" | "budget" | "blocked" | "nothing-pending";
29
+ export interface IdleWakeDeps {
30
+ /** A run, a run claim, or an autopilot cycle is live and will deliver it. */
31
+ isBusy: () => boolean;
32
+ /** The user is deciding something (a plan awaiting review). */
33
+ isBlocked: () => boolean;
34
+ /** An urgent alert is still waiting to be delivered. */
35
+ hasPendingAlert: () => boolean;
36
+ /** Monotonic count of human prompts; budgets and Stop reset when it moves. */
37
+ humanPromptSeq: () => number;
38
+ /** Start the wake run; resolves when it has settled. */
39
+ start: () => Promise<void>;
40
+ /** Observability hook for skipped wakes. */
41
+ onSkip?: (reason: IdleWakeSkip) => void;
42
+ debounceMs?: number;
43
+ maxWakesPerPrompt?: number;
44
+ }
45
+ export declare class IdleWakeController {
46
+ private readonly deps;
47
+ private timer;
48
+ private budgetSeq;
49
+ private wakesUsed;
50
+ private stoppedAtSeq;
51
+ private starting;
52
+ private disposed;
53
+ constructor(deps: IdleWakeDeps);
54
+ /** An urgent alert was queued. Debounced; safe to call often. */
55
+ trigger(): void;
56
+ /** The user pressed Stop: no unattended wakes until their next message. */
57
+ noteStop(): void;
58
+ dispose(): void;
59
+ private cancelTimer;
60
+ private fire;
61
+ private skip;
62
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Wake an idle session when its own background work needs attention.
3
+ *
4
+ * Background alerts — a build or CI watcher that failed, a task that exited
5
+ * non-zero, a child agent that finished — ride the steering path, which only
6
+ * exists while a run is live. Once the agent has ended its turn, an alert sat
7
+ * in the queue until the user happened to send another message (measured: 8 of
8
+ * 483 runs began with a status update that had been waiting for exactly that).
9
+ *
10
+ * This starts a run for the alert instead, inside hard limits:
11
+ *
12
+ * - Never while anything else could deliver it: a live run, a run being
13
+ * claimed, an autopilot cycle. The alert waits for that run's next steering
14
+ * boundary instead, and is retried here if the run ends without draining it.
15
+ * - Never while the user is deciding something (a plan awaiting review).
16
+ * - Never after the user pressed Stop, until they send another message. Stop
17
+ * means "stop acting on your own", and a background exit is not a reason to
18
+ * override it.
19
+ * - At most {@link DEFAULT_MAX_WAKES_PER_PROMPT} wakes per user message, so a
20
+ * flapping task cannot keep the agent (and the bill) running unattended.
21
+ *
22
+ * Pure policy over injected callbacks; the host owns the run itself.
23
+ */
24
+ /** Batch alerts that land together (a failure line, then the exit) into one run. */
25
+ export const DEFAULT_IDLE_WAKE_DEBOUNCE_MS = 2_000;
26
+ /** Unattended wakes allowed per human prompt. */
27
+ export const DEFAULT_MAX_WAKES_PER_PROMPT = 2;
28
+ export class IdleWakeController {
29
+ deps;
30
+ timer = null;
31
+ budgetSeq = -1;
32
+ wakesUsed = 0;
33
+ stoppedAtSeq = null;
34
+ starting = false;
35
+ disposed = false;
36
+ constructor(deps) {
37
+ this.deps = deps;
38
+ }
39
+ /** An urgent alert was queued. Debounced; safe to call often. */
40
+ trigger() {
41
+ if (this.disposed || this.timer !== null)
42
+ return;
43
+ this.timer = setTimeout(() => {
44
+ this.timer = null;
45
+ void this.fire();
46
+ }, this.deps.debounceMs ?? DEFAULT_IDLE_WAKE_DEBOUNCE_MS);
47
+ this.timer.unref?.();
48
+ }
49
+ /** The user pressed Stop: no unattended wakes until their next message. */
50
+ noteStop() {
51
+ this.stoppedAtSeq = this.deps.humanPromptSeq();
52
+ this.cancelTimer();
53
+ }
54
+ dispose() {
55
+ this.disposed = true;
56
+ this.cancelTimer();
57
+ }
58
+ cancelTimer() {
59
+ if (this.timer !== null)
60
+ clearTimeout(this.timer);
61
+ this.timer = null;
62
+ }
63
+ async fire() {
64
+ if (this.disposed || this.starting)
65
+ return;
66
+ if (!this.deps.hasPendingAlert())
67
+ return this.skip("nothing-pending");
68
+ const seq = this.deps.humanPromptSeq();
69
+ if (this.stoppedAtSeq === seq)
70
+ return this.skip("stopped");
71
+ if (seq !== this.budgetSeq) {
72
+ this.budgetSeq = seq;
73
+ this.wakesUsed = 0;
74
+ }
75
+ if (this.wakesUsed >= (this.deps.maxWakesPerPrompt ?? DEFAULT_MAX_WAKES_PER_PROMPT)) {
76
+ return this.skip("budget");
77
+ }
78
+ if (this.deps.isBusy()) {
79
+ // The live run should drain it at its next steering boundary. If that
80
+ // run is already past its last boundary, the alert would strand — so
81
+ // look again shortly rather than trusting the run to have taken it.
82
+ this.trigger();
83
+ return;
84
+ }
85
+ if (this.deps.isBlocked())
86
+ return this.skip("blocked");
87
+ this.wakesUsed += 1;
88
+ this.starting = true;
89
+ try {
90
+ await this.deps.start();
91
+ }
92
+ finally {
93
+ this.starting = false;
94
+ }
95
+ // An alert that landed after the wake run's last steering boundary.
96
+ if (this.deps.hasPendingAlert())
97
+ this.trigger();
98
+ }
99
+ skip(reason) {
100
+ this.deps.onSkip?.(reason);
101
+ }
102
+ }
103
+ //# sourceMappingURL=idle-wake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-wake.js","sourceRoot":"","sources":["../../src/core/idle-wake.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,11 @@ export interface BackgroundProcess {
10
10
  startedAt: number;
11
11
  exitCode: number | null;
12
12
  lastReadOffset: number;
13
- /** Unread one-shot wake, retained even if it fired before task_output waited. */
14
- wakeReason?: "pattern" | "silence";
13
+ /** Unread wake, retained even if it fired before task_output waited. */
14
+ wakeReason?: WakeReason;
15
+ /** Log offset the wake is about. A read that reaches it consumes the wake;
16
+ * one that started earlier does not (the wake fired mid-read). */
17
+ wakeOffset?: number;
15
18
  /**
16
19
  * Last known size of `logFile` in bytes. Kept current by the progress
17
20
  * watcher, the exit handler and every `readOutput`, so consumers (notably
@@ -37,6 +40,17 @@ export interface ReadOutputResult {
37
40
  /** Ceiling on a single blocking `waitForExitOrWake`, so one wedged process cannot
38
41
  * hold the agent loop indefinitely; callers re-wait if they still want to. */
39
42
  export declare const MAX_PROCESS_WAIT_MS = 600000;
43
+ /**
44
+ * Remove terminal colour and cursor codes. Test runners and CLIs colour their
45
+ * output, and a code can sit inside the very word a wake looks for:
46
+ * `\e[31mFAIL\e[0mED` never matches /FAILED/ until it is stripped.
47
+ */
48
+ export declare function stripAnsi(text: string): string;
49
+ /**
50
+ * The first line in `text` that looks like a tool reporting a failure, ANSI
51
+ * codes stripped, or undefined. Exported for tests.
52
+ */
53
+ export declare function findFailureLine(text: string): string | undefined;
40
54
  /**
41
55
  * Model-declared wake conditions for a background task. The agent states up
42
56
  * front what output it cares about (or what silence means), and the watcher
@@ -45,12 +59,22 @@ export declare const MAX_PROCESS_WAIT_MS = 600000;
45
59
  * or re-reading generic progress checkpoints hoping to spot the signal.
46
60
  */
47
61
  export interface WakeRules {
48
- /** Wake the moment new log output matches this regex. One-shot. */
62
+ /** Wake whenever new log output matches this regex — every new match, not
63
+ * just the first, so a dev server that breaks again after a fix is seen. */
49
64
  pattern?: RegExp;
50
65
  /** Wake when the process is still running but has logged nothing for this
51
66
  * many milliseconds (a stalled build/hang detector). One-shot. */
52
67
  silenceMs?: number;
53
68
  }
69
+ /** Why a wake fired. `failure` is the built-in detector, armed on every task. */
70
+ export type WakeReason = "pattern" | "silence" | "failure";
71
+ /** How a blocking wait on one background task ended. */
72
+ export type WaitOutcome = "exited" | "timeout" | "unknown" | WakeReason | "other";
73
+ export interface WaitResult {
74
+ reason: WaitOutcome;
75
+ /** For `other`: the notification about a different task that ended the wait. */
76
+ detail?: string;
77
+ }
54
78
  export interface ProcessManagerOps {
55
79
  platform?: NodeJS.Platform;
56
80
  kill?: typeof process.kill;
@@ -94,6 +118,9 @@ export declare class ProcessManager {
94
118
  private wakeStates;
95
119
  /** Log size at the last emitted checkpoint, so a quiet process stays quiet. */
96
120
  private watchedSizes;
121
+ /** Processes the agent or host is deliberately stopping: their non-zero exit
122
+ * is the stop working, not news, so it must not raise an urgent alert. */
123
+ private stopping;
97
124
  /** Timestamp of the last retention sweep; 0 means "never swept". */
98
125
  private lastPruneAt;
99
126
  constructor(ops?: ProcessManagerOps);
@@ -149,16 +176,22 @@ export declare class ProcessManager {
149
176
  /** Read the trailing bytes of a log without loading the whole file. */
150
177
  private readTail;
151
178
  /** Read the bytes of a log in `[start, end)` without loading the file. */
152
- private readRange;
179
+ private readBytes;
153
180
  /**
154
- * Wake-rule watcher: evaluates the model's `pattern`/`silenceMs` conditions
155
- * every {@link WAKE_INTERVAL_MS} and pushes a steering-path notification the
156
- * moment one holds. Each rule is one-shot; once every declared rule has fired
157
- * (or the process exits) the watcher retires. Unlike the progress watcher it
158
- * never backs off — the agent asked for exactly this signal, however long it
159
- * takes, and a late match on a quiet dev server is precisely the point.
181
+ * Wake watcher, evaluated every {@link WAKE_INTERVAL_MS}. Always runs the
182
+ * built-in failure detector (one-shot), plus the model's declared rules when
183
+ * a notification queue can deliver them: `pattern` fires on every new match,
184
+ * `silenceMs` once. It retires on exit, or once nothing is left that could
185
+ * still fire. Unlike the progress watcher it never backs off — a late match
186
+ * on a quiet dev server is precisely the point.
187
+ *
188
+ * Returns whether the model's declared rules were armed. Without a queue they
189
+ * are not (nothing could deliver them), but failure detection still releases
190
+ * a blocking `task_output` wait directly.
160
191
  */
161
192
  private armWakeWatcher;
193
+ /** Record a wake on the process and release anyone blocked waiting on it. */
194
+ private fireWake;
162
195
  private evaluateWakeRules;
163
196
  /** Stop and forget a process's wake watcher. */
164
197
  private disposeWakeWatcher;
@@ -182,7 +215,14 @@ export declare class ProcessManager {
182
215
  * loop step to be delivered, which is exactly what an agent with nothing
183
216
  * else to do does not have.
184
217
  */
185
- waitForExitOrWake(id: string, timeoutMs: number, signal?: AbortSignal): Promise<"exited" | "timeout" | "unknown" | "pattern" | "silence">;
218
+ waitForExitOrWake(id: string, timeoutMs: number, signal?: AbortSignal): Promise<WaitOutcome>;
219
+ /**
220
+ * {@link waitForExitOrWake}, plus what ended it. Besides this task exiting or
221
+ * waking, the wait also ends when ANOTHER task raises an urgent notification
222
+ * (a different build failed, a child agent finished): the agent blocked here
223
+ * has no other way to learn that before its wait runs out.
224
+ */
225
+ waitForActivity(id: string, timeoutMs: number, signal?: AbortSignal): Promise<WaitResult>;
186
226
  readOutput(id: string, fromStart?: boolean): Promise<ReadOutputResult>;
187
227
  /**
188
228
  * Write input to a running background process's stdin, enabling interactive