@code-yeongyu/senpi 2026.10.11 → 2026.10.12

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 (188) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/bundle/chunks/anthropic-messages-GYG6YZY5.js +10 -0
  3. package/dist/bundle/chunks/{app-server-command-NHDOL4HQ.js → app-server-command-OL6PPUOA.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-EJF2XJ7Q.js → azure-openai-responses-GU6FOSJR.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-ZABLYCHT.js → chunk-46JEQXD7.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-VE3D74HN.js → chunk-55VKYA5V.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-7UNN3YHW.js → chunk-5AVFHHDF.js} +230 -221
  8. package/dist/bundle/chunks/{chunk-2JE2DOIF.js → chunk-5DPTM5E2.js} +1 -1
  9. package/dist/bundle/chunks/chunk-5UPUSGP5.js +2 -0
  10. package/dist/bundle/chunks/chunk-5VMRMY6N.js +16 -0
  11. package/dist/bundle/chunks/{chunk-7X3VRAM3.js → chunk-7P3QPVTF.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-T6IYKWIH.js → chunk-BUGZZVQW.js} +1 -1
  13. package/dist/bundle/chunks/chunk-DL7EFVT2.js +12 -0
  14. package/dist/bundle/chunks/chunk-E5YUVGLQ.js +11 -0
  15. package/dist/bundle/chunks/{chunk-RLFQICEY.js → chunk-EQJY2B5F.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-VYKBN4MR.js → chunk-GC2RU5TS.js} +1 -1
  17. package/dist/bundle/chunks/chunk-GPKEJOTQ.js +202 -0
  18. package/dist/bundle/chunks/{chunk-KCVSSFOV.js → chunk-GQ5PWY6P.js} +4 -3
  19. package/dist/bundle/chunks/{chunk-4GPJOGXZ.js → chunk-I4AEC6MH.js} +1 -1
  20. package/dist/bundle/chunks/chunk-JXSFGRBE.js +8 -0
  21. package/dist/bundle/chunks/{chunk-YGCMN2CX.js → chunk-NAY5LB53.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-EBOD66AS.js → chunk-PBN2CATL.js} +2 -2
  23. package/dist/bundle/chunks/chunk-PFNBY43W.js +2 -0
  24. package/dist/bundle/chunks/{chunk-CY2B4QRR.js → chunk-PUWEQK2W.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-5DBPUQSX.js → chunk-SF6AUNQZ.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-G2XAU4BX.js → chunk-TMTT5CLA.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-2JKDZXIY.js → chunk-VWFKLW73.js} +2 -2
  28. package/dist/bundle/chunks/chunk-WZMSBRTW.js +2 -0
  29. package/dist/bundle/chunks/{chunk-55HLKZSB.js → chunk-XVJRRLGN.js} +1 -1
  30. package/dist/bundle/chunks/chunk-Z2PWWBBD.js +31 -0
  31. package/dist/bundle/chunks/cli-main-RSIYAR4J.js +3 -0
  32. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-SWVU2AXJ.js → cloudflare-workers-ai-system-one-LVWRKOUD.js} +1 -1
  33. package/dist/bundle/chunks/{google-generative-ai-XG4YX6FQ.js → google-generative-ai-JMGYPTXL.js} +1 -1
  34. package/dist/bundle/chunks/{google-vertex-BBOZ6OUB.js → google-vertex-CI4FPIU7.js} +1 -1
  35. package/dist/bundle/chunks/{help-fast-path-EZ3KEW6V.js → help-fast-path-M7KMBM6K.js} +1 -1
  36. package/dist/bundle/chunks/host-command-6UR3DSFR.js +2 -0
  37. package/dist/bundle/chunks/host-lifecycle-2BKCCK5X.js +3 -0
  38. package/dist/bundle/chunks/host-lifecycle.js +10 -7
  39. package/dist/bundle/chunks/host-session-command-A2MZHEBP.js +19 -0
  40. package/dist/bundle/chunks/interactive-mode-EJBMTJOH.js +2 -0
  41. package/dist/bundle/chunks/{list-tips-WE2YGQM6.js → list-tips-BEQAOA4N.js} +1 -1
  42. package/dist/bundle/chunks/{mistral-conversations-RB3ZBCGE.js → mistral-conversations-5OD7662T.js} +1 -1
  43. package/dist/bundle/chunks/{multi-session-host-V4CUQLV7.js → multi-session-host-YYMLHCUA.js} +4 -4
  44. package/dist/bundle/chunks/{openai-codex-responses-DBTPUXRW.js → openai-codex-responses-FN3UPAFI.js} +1 -1
  45. package/dist/bundle/chunks/{openai-completions-QAJQC4BY.js → openai-completions-KMUQ3T4M.js} +1 -1
  46. package/dist/bundle/chunks/{openai-responses-UBJWCEHG.js → openai-responses-V4Q5GVLO.js} +1 -1
  47. package/dist/bundle/chunks/{package-manager-cli-WTRDT4ZH.js → package-manager-cli-6A5ZP26E.js} +1 -1
  48. package/dist/bundle/chunks/rotation-stream-PT6LLBMJ.js +2 -0
  49. package/dist/bundle/chunks/rpc-mode-XNYJPYUM.js +2 -0
  50. package/dist/bundle/chunks/runtime-session-ZBHFMT65.js +2 -0
  51. package/dist/bundle/chunks/{schedule-command-PJVRKAKD.js → schedule-command-FYQG2MFX.js} +1 -1
  52. package/dist/bundle/chunks/session-control-endpoint-7TX4XHN4.js +3 -0
  53. package/dist/bundle/chunks/session-picker-4RNB27SM.js +2 -0
  54. package/dist/bundle/chunks/session-worker.js +418 -412
  55. package/dist/bundle/chunks/{typesafe-system-one-B5TW3FHM.js → typesafe-system-one-WUHW7HR4.js} +1 -1
  56. package/dist/bundle/cli.js +1 -1
  57. package/dist/bundle/index.js +1 -1
  58. package/dist/bundle/rpc-entry.js +1 -1
  59. package/dist/bundle/runtime-manifest.json +1 -1
  60. package/dist/core/agent-session.d.ts +11 -2
  61. package/dist/core/agent-session.js +152 -43
  62. package/dist/core/compaction/compaction.js +2 -0
  63. package/dist/core/extensions/builtin/anthropic-web-search/index.js +22 -14
  64. package/dist/core/extensions/builtin/cache-keepalive/index.js +8 -4
  65. package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.d.ts +2 -1
  66. package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.js +55 -7
  67. package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.d.ts +8 -0
  68. package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.js +25 -0
  69. package/dist/core/extensions/builtin/compaction/index.js +13 -5
  70. package/dist/core/extensions/builtin/compaction/speculative.d.ts +1 -0
  71. package/dist/core/extensions/builtin/compaction/speculative.js +8 -4
  72. package/dist/core/extensions/builtin/moved-path-guard/tool-classes.js +1 -0
  73. package/dist/core/extensions/builtin/openai-web-search/index.d.ts +4 -1
  74. package/dist/core/extensions/builtin/openai-web-search/index.js +54 -17
  75. package/dist/core/extensions/builtin/server-search-fallback.d.ts +12 -0
  76. package/dist/core/extensions/builtin/server-search-fallback.js +50 -0
  77. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +10 -1
  78. package/dist/core/extensions/builtin/terminal/runtime-session.js +48 -4
  79. package/dist/core/extensions/builtin/websearch/index.d.ts +1 -1
  80. package/dist/core/extensions/builtin/websearch/index.js +119 -18
  81. package/dist/core/extensions/builtin/websearch/server-search.d.ts +11 -0
  82. package/dist/core/extensions/builtin/websearch/server-search.js +230 -0
  83. package/dist/core/extensions/builtin/websearch/websearch/native.d.ts +2 -0
  84. package/dist/core/extensions/builtin/websearch/websearch/search-model.js +15 -0
  85. package/dist/core/extensions/runner.d.ts +8 -1
  86. package/dist/core/extensions/runner.js +56 -7
  87. package/dist/core/extensions/types.d.ts +12 -2
  88. package/dist/core/extensions/user-activity.d.ts +10 -0
  89. package/dist/core/extensions/user-activity.js +20 -0
  90. package/dist/core/model-config-schema.d.ts +34 -0
  91. package/dist/core/model-config-schema.js +2 -0
  92. package/dist/core/model-config.js +1 -0
  93. package/dist/core/reserved-keywords.d.ts +28 -0
  94. package/dist/core/reserved-keywords.js +81 -0
  95. package/dist/core/retry-fallback/candidates.d.ts +1 -1
  96. package/dist/core/retry-fallback/candidates.js +1 -12
  97. package/dist/core/retry-fallback/controller.d.ts +3 -1
  98. package/dist/core/retry-fallback/controller.js +11 -5
  99. package/dist/core/retry-fallback/cooldown.d.ts +2 -1
  100. package/dist/core/retry-fallback/cooldown.js +21 -12
  101. package/dist/core/retry-fallback/unusable.d.ts +2 -0
  102. package/dist/core/retry-fallback/unusable.js +14 -2
  103. package/dist/core/retry-fallback/usage-limit.d.ts +10 -1
  104. package/dist/core/retry-fallback/usage-limit.js +39 -2
  105. package/dist/core/session-title-generator.js +2 -0
  106. package/dist/index.d.ts +2 -1
  107. package/dist/index.js +1 -0
  108. package/dist/modes/index.d.ts +1 -1
  109. package/dist/modes/interactive/components/assistant-render-descriptors.js +7 -1
  110. package/dist/modes/interactive/composer-user-activity.d.ts +6 -0
  111. package/dist/modes/interactive/composer-user-activity.js +30 -0
  112. package/dist/modes/interactive/grok/chrome.js +2 -1
  113. package/dist/modes/interactive/interactive-mode.js +15 -6
  114. package/dist/modes/interactive/session-control-registry.js +8 -1
  115. package/dist/modes/interactive/theme/reserved-keyword-rainbow.d.ts +11 -0
  116. package/dist/modes/interactive/theme/reserved-keyword-rainbow.js +50 -0
  117. package/dist/modes/interactive/theme/theme.d.ts +3 -1
  118. package/dist/modes/interactive/theme/theme.js +9 -0
  119. package/dist/modes/print-mode.js +7 -2
  120. package/dist/modes/rpc/connection-handler.js +43 -11
  121. package/dist/modes/rpc/custom-capability.d.ts +2 -0
  122. package/dist/modes/rpc/custom-capability.js +2 -0
  123. package/dist/modes/rpc/host-admission-proxy.d.ts +9 -0
  124. package/dist/modes/rpc/host-admission-proxy.js +211 -0
  125. package/dist/modes/rpc/host-admission.d.ts +21 -0
  126. package/dist/modes/rpc/host-admission.js +59 -0
  127. package/dist/modes/rpc/host-capabilities.js +2 -1
  128. package/dist/modes/rpc/host-client-occupancy.d.ts +2 -1
  129. package/dist/modes/rpc/host-client-occupancy.js +7 -0
  130. package/dist/modes/rpc/host-decision.d.ts +2 -2
  131. package/dist/modes/rpc/host-decision.js +4 -0
  132. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  133. package/dist/modes/rpc/host-ensure.js +31 -3
  134. package/dist/modes/rpc/host-lifecycle-drain.d.ts +2 -1
  135. package/dist/modes/rpc/host-lifecycle-drain.js +3 -1
  136. package/dist/modes/rpc/host-lifecycle-proxy.d.ts +3 -2
  137. package/dist/modes/rpc/host-lifecycle-proxy.js +10 -19
  138. package/dist/modes/rpc/host-lifecycle-shutdown.d.ts +2 -0
  139. package/dist/modes/rpc/host-lifecycle-shutdown.js +3 -1
  140. package/dist/modes/rpc/host-lifecycle-ticker.d.ts +11 -0
  141. package/dist/modes/rpc/host-lifecycle-ticker.js +27 -0
  142. package/dist/modes/rpc/host-lifecycle.js +15 -23
  143. package/dist/modes/rpc/host-outcome.js +1 -0
  144. package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
  145. package/dist/modes/rpc/host-protocol-info.js +1 -0
  146. package/dist/modes/rpc/host-session-runner.js +22 -1
  147. package/dist/modes/rpc/host-status-all.js +1 -0
  148. package/dist/modes/rpc/host-status.d.ts +1 -0
  149. package/dist/modes/rpc/host-status.js +5 -1
  150. package/dist/modes/rpc/rpc-client.d.ts +7 -1
  151. package/dist/modes/rpc/rpc-client.js +12 -0
  152. package/dist/modes/rpc/rpc-types.d.ts +49 -1
  153. package/dist/modes/rpc/rpc-types.js +5 -0
  154. package/dist/modes/rpc/session-command-activity.js +2 -0
  155. package/dist/modes/rpc/session-held.js +3 -0
  156. package/docs/extensions.md +13 -1
  157. package/docs/providers.md +88 -0
  158. package/docs/rpc.md +150 -5
  159. package/docs/web-search.md +3 -1
  160. package/package.json +7 -7
  161. package/dist/bundle/chunks/anthropic-messages-5QQJVQPZ.js +0 -9
  162. package/dist/bundle/chunks/chunk-2A3MS2Z7.js +0 -8
  163. package/dist/bundle/chunks/chunk-6SBY4ZWC.js +0 -2
  164. package/dist/bundle/chunks/chunk-6WCOSBI7.js +0 -2
  165. package/dist/bundle/chunks/chunk-E75RO54O.js +0 -14
  166. package/dist/bundle/chunks/chunk-JMYO34RK.js +0 -2
  167. package/dist/bundle/chunks/chunk-KRNXUTZJ.js +0 -2
  168. package/dist/bundle/chunks/chunk-L4HVJWIF.js +0 -11
  169. package/dist/bundle/chunks/chunk-N5WLLHUE.js +0 -2
  170. package/dist/bundle/chunks/chunk-NEFNNQSV.js +0 -9
  171. package/dist/bundle/chunks/chunk-PWIYVLIA.js +0 -9
  172. package/dist/bundle/chunks/chunk-Q3R44CGT.js +0 -5
  173. package/dist/bundle/chunks/chunk-QIARZN7G.js +0 -31
  174. package/dist/bundle/chunks/chunk-QKU6JOA4.js +0 -2
  175. package/dist/bundle/chunks/chunk-QYWXDE47.js +0 -2
  176. package/dist/bundle/chunks/chunk-XHELSLN3.js +0 -188
  177. package/dist/bundle/chunks/chunk-YDJ4HGWO.js +0 -16
  178. package/dist/bundle/chunks/chunk-ZJHRDQCH.js +0 -2
  179. package/dist/bundle/chunks/cli-main-TUBNBZNK.js +0 -3
  180. package/dist/bundle/chunks/host-command-WO7VKJBJ.js +0 -2
  181. package/dist/bundle/chunks/host-lifecycle-X2J5FTJL.js +0 -3
  182. package/dist/bundle/chunks/host-session-command-ICBOH5RP.js +0 -19
  183. package/dist/bundle/chunks/interactive-mode-UGDNCGQ5.js +0 -2
  184. package/dist/bundle/chunks/rotation-stream-OODURUGZ.js +0 -2
  185. package/dist/bundle/chunks/rpc-mode-22MCT32O.js +0 -2
  186. package/dist/bundle/chunks/runtime-session-I4NDNGC5.js +0 -2
  187. package/dist/bundle/chunks/session-control-endpoint-SFPRQBDO.js +0 -3
  188. package/dist/bundle/chunks/session-picker-3ZQPV7Z3.js +0 -2
@@ -1,3 +1,5 @@
1
+ import { type AssistantMessage } from "@earendil-works/pi-ai";
2
+ import type { SessionEntry } from "../session-manager.ts";
1
3
  /**
2
4
  * Who a usage limit binds. `model`: the provider refused THIS model only (a
3
5
  * Fable-only weekly cap, premium models on a Copilot plan, a model that needs
@@ -7,7 +9,14 @@
7
9
  * the chain moves to another provider instead.
8
10
  */
9
11
  export type UsageLimitScope = "model" | "account";
10
- export declare function usageLimitScope(errorMessage: string | undefined): UsageLimitScope | undefined;
12
+ export declare class AccountUsageLimitError extends Error {
13
+ constructor(provider: string, raw?: string);
14
+ }
15
+ export declare function usageLimitScope(failure: string | undefined | Pick<AssistantMessage, "errorMessage" | "errorCause">): UsageLimitScope | undefined;
16
+ /** Project advice without mutating the diagnostic-bearing engine message. */
17
+ export declare function describeUsageLimitMessage(message: AssistantMessage): AssistantMessage;
18
+ /** Project an assistant entry without mutating its persisted identity or payload. */
19
+ export declare function describeUsageLimitEntry(entry: SessionEntry): SessionEntry;
11
20
  /** The notice clause naming what hit the limit, or undefined when no usage limit caused the switch. */
12
21
  export declare function usageLimitCause(from: string, limit: UsageLimitScope | undefined): string | undefined;
13
22
  //# sourceMappingURL=usage-limit.d.ts.map
@@ -1,6 +1,12 @@
1
- import { isQuotaExhaustionMessage } from "@earendil-works/pi-ai";
1
+ import { describeProviderFailureForUser, isQuotaExhaustionMessage } from "@earendil-works/pi-ai";
2
2
  import { rateLimitModelFamily } from "../credential-pool/model-scope.js";
3
3
  import { isBillingErrorMessage } from "./billing.js";
4
+ export class AccountUsageLimitError extends Error {
5
+ constructor(provider, raw = "usage limit reached") {
6
+ super(describeProviderFailureForUser(raw, { model: provider, usageLimit: "account" }));
7
+ this.name = "AccountUsageLimitError";
8
+ }
9
+ }
4
10
  // Subscription limit prose that carries no quota/billing keyword, e.g. Claude's
5
11
  // "You've hit your session limit · resets 3pm" and the SDK terminal reasons.
6
12
  const SUBSCRIPTION_LIMIT_PATTERN = /\bhit\s+your\b[^.]*\blimit\b|\b(?:session|weekly|monthly|daily|hourly|\d+[- ]hour|usage)\s+limit\b|\bblocking_limit\b|\brapid_refill_breaker\b/i;
@@ -8,7 +14,13 @@ const SUBSCRIPTION_LIMIT_PATTERN = /\bhit\s+your\b[^.]*\blimit\b|\b(?:session|we
8
14
  // switch to a different model" (senpi#2660). An ordinary "rate limit exceeded" stays transient.
9
15
  const PLAN_MODEL_LIMIT_PATTERN = /\breached\b[^.]*\b(?:free|plan|tier|model)\b[^.]*\blimit\b[\s\S]*\bswitch to (?:a |an )?(?:different|another) model\b/i;
10
16
  const MODEL_SCOPE_PATTERN = /\bmodels?\b|\bpremium\b|\b(?:opus|sonnet|haiku|fable|mythos)\b/i;
11
- export function usageLimitScope(errorMessage) {
17
+ export function usageLimitScope(failure) {
18
+ if (typeof failure === "object" && failure.errorCause?.type === "usage_limit") {
19
+ const scope = failure.errorCause.scope;
20
+ if (scope === "account" || scope === "model")
21
+ return scope;
22
+ }
23
+ const errorMessage = typeof failure === "object" ? failure.errorMessage : failure;
12
24
  if (errorMessage === undefined)
13
25
  return undefined;
14
26
  // Claude Code's "You've reached your Fable limit. Switch to another model" carries
@@ -23,6 +35,31 @@ export function usageLimitScope(errorMessage) {
23
35
  return undefined;
24
36
  return MODEL_SCOPE_PATTERN.test(errorMessage) ? "model" : "account";
25
37
  }
38
+ /** Project advice without mutating the diagnostic-bearing engine message. */
39
+ export function describeUsageLimitMessage(message) {
40
+ const scope = usageLimitScope(message);
41
+ if (!scope || message.errorCause?.scope === scope)
42
+ return message;
43
+ return {
44
+ ...message,
45
+ errorCause: { type: "usage_limit", scope },
46
+ ...(scope === "account"
47
+ ? {
48
+ errorMessage: describeProviderFailureForUser(message.errorMessage, {
49
+ model: `${message.provider}/${message.model}`,
50
+ usageLimit: scope,
51
+ }),
52
+ }
53
+ : {}),
54
+ };
55
+ }
56
+ /** Project an assistant entry without mutating its persisted identity or payload. */
57
+ export function describeUsageLimitEntry(entry) {
58
+ if (entry.type !== "message" || entry.message.role !== "assistant")
59
+ return entry;
60
+ const message = describeUsageLimitMessage(entry.message);
61
+ return message === entry.message ? entry : { ...entry, message };
62
+ }
26
63
  /** The notice clause naming what hit the limit, or undefined when no usage limit caused the switch. */
27
64
  export function usageLimitCause(from, limit) {
28
65
  if (limit === "model")
@@ -1,5 +1,6 @@
1
1
  import { normalizeContext } from "@earendil-works/pi-ai";
2
2
  import { completeSimple, retryAssistantCall } from "@earendil-works/pi-ai/compat";
3
+ import { withoutServerSearch } from "./extensions/builtin/server-search-fallback.js";
3
4
  import { initialTitleReasoning, mandatoryReasoningRetryLevel } from "./session-title-reasoning.js";
4
5
  const TITLE_SYSTEM_PROMPT = `Generate a concise title for this coding-agent session.
5
6
 
@@ -93,6 +94,7 @@ function buildTitleContext(firstPrompt) {
93
94
  function buildTitleOptions(options, reasoning) {
94
95
  const titleOptions = {
95
96
  ...options.baseOptions,
97
+ onPayload: withoutServerSearch(options.baseOptions?.onPayload),
96
98
  sessionId: options.sessionId,
97
99
  cacheRetention: options.model.cacheRetention === "none" ? "none" : "short",
98
100
  maxTokens: reasoning === undefined ? TITLE_MAX_TOKENS : REASONING_TITLE_MAX_TOKENS,
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { type CreateModelRuntimeOptions, CredentialSynchronizationError, type Cr
21
21
  export type { PackageManager, PathMetadata, ProgressCallback, ProgressEvent, ResolvedPaths, ResolvedResource, } from "./core/package-manager.ts";
22
22
  export { DefaultPackageManager } from "./core/package-manager.ts";
23
23
  export { type ProcessFootprint, type ProcessFootprintMeasure, readOwnFootprint, readProcessFootprint, } from "./core/process-footprint.ts";
24
+ export { findReservedKeywordRuns, findReservedKeywordSpans, type ReservedKeyword, type ReservedKeywordRun, type ReservedKeywordSpan, } from "./core/reserved-keywords.ts";
24
25
  export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./core/resource-loader.ts";
25
26
  export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.ts";
26
27
  export { AgentSessionRuntime, type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionOptions, type CreateAgentSessionResult, type CreateAgentSessionRuntimeFactory, type CreateAgentSessionRuntimeResult, type CreateAgentSessionServicesOptions, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createPowerShellTool, createReadOnlyTools, createReadTool, createWriteTool, type PromptTemplate, } from "./core/sdk.ts";
@@ -36,7 +37,7 @@ export { UnknownCommandError, type UnknownCommandReason } from "./core/unknown-c
36
37
  export { type ModelRoute, type ModelRouteReason, type ModelRouteRequest, VIRTUAL_MODEL_STATE_ENTRY, type VirtualModelDefinition, type VirtualModelStateData, } from "./core/virtual-models.ts";
37
38
  export { connectWebViewService, type WebViewServiceConnection } from "./core/webview/webview-broker.ts";
38
39
  export { type MainOptions, main } from "./main.ts";
39
- export { classifyEndpointLiveness, createHostDaemonPaths, DEFAULT_HOST_LAUNCH_SPEC, daemonDirectoryName, decideHostAction, ENDPOINT_REGISTRY_VERSION, type EndpointKind, type EndpointLiveness, type EnsuredHost, type EnsureHostOptions, endpointProbeTimeoutMs, ensureHost, GENERATION_HANDOFF_CAPABILITY, gcHostEndpoints, type HandoffHostOptions, type HandoffRefusal, type HandoffResult, HOST_EXIT_ERROR, HOST_EXIT_FALLBACK, HOST_EXIT_OK, HOST_EXIT_REFUSED, HOST_EXIT_USAGE, HOST_PROTOCOL_VERSION, type HostAction, type HostDaemonPaths, type HostDecision, type HostDecisionClient, type HostDecisionPolicy, type HostDecisionWarning, type HostEndpointEntry, type HostEndpointIdentitySource, type HostEndpointStatus, HostEnsureRefusedError, type HostGcOptions, type HostGcResult, type HostGenerationRow, type HostLaunchSpec, type HostLaunchSpecCore, HostLaunchSpecError, type HostLaunchSpecRefusal, type HostOutcome, type HostProtocolInfo, type HostRefusalReason, type HostRequest, type HostSessionCounts, type HostStatusOptions, type HostStatusReport, type HostTarget, type HostUpgradePolicy, handoffHost, InteractiveMode, type InteractiveModeOptions, isTransportGoneError, type JsonAgentSessionEvent, listHostEndpoints, loadHostLaunchSpec, type ModelInfo, PINNED_HOST_CLIENT_CAPABILITIES, type PrintModeOptions, type ProbeHostOptions, parseHostLaunchSpec, parseHostProtocolInfo, probeHost, REQUIRED_HOST_CAPABILITIES, type ResolvedHostLaunchSpec, RpcClient, type RpcClientEvent, RpcClientOpenInFlightError, type RpcClientOptions, type RpcCommand, type RpcEventListener, type RpcExtensionEvent, type RpcExtensionUIRequest, type RpcExtensionUIResponse, type RpcResponse, type RpcSessionState, RpcTransportGoneError, readAllHostStatus, readHostStatus, runHostRequest, runPrintMode, runRpcMode, type ShardKind, type StopHostOptions, type StopHostResult, shardKey, shardSocketPath, shardSocketPathForKey, stopHost, TUI_PROBE_TIMEOUT_MS, } from "./modes/index.ts";
40
+ export { classifyEndpointLiveness, createHostDaemonPaths, DEFAULT_HOST_LAUNCH_SPEC, daemonDirectoryName, decideHostAction, ENDPOINT_REGISTRY_VERSION, type EndpointKind, type EndpointLiveness, type EnsuredHost, type EnsureHostOptions, endpointProbeTimeoutMs, ensureHost, GENERATION_HANDOFF_CAPABILITY, gcHostEndpoints, type HandoffHostOptions, type HandoffRefusal, type HandoffResult, HOST_EXIT_ERROR, HOST_EXIT_FALLBACK, HOST_EXIT_OK, HOST_EXIT_REFUSED, HOST_EXIT_USAGE, HOST_PROTOCOL_VERSION, type HostAction, type HostDaemonPaths, type HostDecision, type HostDecisionClient, type HostDecisionPolicy, type HostDecisionWarning, type HostEndpointEntry, type HostEndpointIdentitySource, type HostEndpointStatus, HostEnsureRefusedError, type HostGcOptions, type HostGcResult, type HostGenerationRow, type HostLaunchSpec, type HostLaunchSpecCore, HostLaunchSpecError, type HostLaunchSpecRefusal, type HostOutcome, type HostProtocolInfo, type HostRefusalReason, type HostRequest, type HostSessionCounts, type HostStatusOptions, type HostStatusReport, type HostTarget, type HostUpgradePolicy, handoffHost, InteractiveMode, type InteractiveModeOptions, isTransportGoneError, type JsonAgentSessionEvent, listHostEndpoints, loadHostLaunchSpec, type ModelInfo, PINNED_HOST_CLIENT_CAPABILITIES, type PrintModeOptions, type ProbeHostOptions, parseHostLaunchSpec, parseHostProtocolInfo, probeHost, REQUIRED_HOST_CAPABILITIES, type ResolvedHostLaunchSpec, RpcClient, type RpcClientEvent, RpcClientOpenInFlightError, type RpcClientOptions, type RpcCommand, type RpcEvalCell, type RpcEvalCellSnapshot, type RpcEventListener, type RpcExtensionEvent, type RpcExtensionUIRequest, type RpcExtensionUIResponse, type RpcResponse, type RpcSessionState, RpcTransportGoneError, readAllHostStatus, readHostStatus, runHostRequest, runPrintMode, runRpcMode, type ShardKind, type StopHostOptions, type StopHostResult, shardKey, shardSocketPath, shardSocketPathForKey, stopHost, TUI_PROBE_TIMEOUT_MS, } from "./modes/index.ts";
40
41
  export { ArminComponent, AssistantMessageComponent, BashExecutionComponent, BorderedLoader, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomEditor, type CustomEditorOptions, CustomMessageComponent, DynamicBorder, ExtensionEditorComponent, ExtensionInputComponent, ExtensionSelectorComponent, FooterComponent, keyHint, keyText, LoginDialogComponent, ModelSelectorComponent, OAuthSelectorComponent, type RenderDiffOptions, rawKeyHint, renderDiff, SessionSelectorComponent, type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent, ShowImagesSelectorComponent, SkillInvocationMessageComponent, ThemeSelectorComponent, ThinkingSelectorComponent, ToolExecutionComponent, type ToolExecutionOptions, TreeSelectorComponent, truncateToVisualLines, UserMessageComponent, UserMessageSelectorComponent, type VisualTruncateResult, } from "./modes/interactive/components/index.ts";
41
42
  export { getLanguageFromPath, getMarkdownTheme, getSelectListTheme, getSettingsListTheme, highlightCode, initTheme, Theme, type ThemeAppearance, type ThemeBg, type ThemeColor, type ThemeStyle, type ThemeToken, } from "./modes/interactive/theme/theme.ts";
42
43
  export { collectOrphanedChildren } from "./modes/rpc/child-reaper.ts";
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export { CredentialSynchronizationError, ModelRuntime, } from "./core/model-runt
25
25
  export { DefaultPackageManager } from "./core/package-manager.js";
26
26
  // Process memory footprint (phys_footprint / RssAnon / PrivateUsage), read without spawning
27
27
  export { readOwnFootprint, readProcessFootprint, } from "./core/process-footprint.js";
28
+ export { findReservedKeywordRuns, findReservedKeywordSpans, } from "./core/reserved-keywords.js";
28
29
  export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.js";
29
30
  // SDK for programmatic usage
30
31
  export { AgentSessionRuntime,
@@ -19,5 +19,5 @@ export { type HostEndpointStatus, readAllHostStatus } from "./rpc/host-status-al
19
19
  export { type StopHostOptions, type StopHostResult, stopHost } from "./rpc/host-stop.ts";
20
20
  export { isTransportGoneError, type ModelInfo, RpcClient, type RpcClientEvent, RpcClientOpenInFlightError, type RpcClientOptions, type RpcEventListener, RpcTransportGoneError, } from "./rpc/rpc-client.ts";
21
21
  export { runRpcMode } from "./rpc/rpc-mode.ts";
22
- export type { RpcCommand, RpcExtensionEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionState, } from "./rpc/rpc-types.ts";
22
+ export type { RpcCommand, RpcEvalCell, RpcEvalCellSnapshot, RpcExtensionEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionState, } from "./rpc/rpc-types.ts";
23
23
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { describeProviderFailureForUser, SERVER_FALLBACK_ABORTED_DIAGNOSTIC, stripTurnRetrySuppressionPrefix, } from "@earendil-works/pi-ai";
2
+ import { usageLimitScope } from "../../../core/retry-fallback/usage-limit.js";
2
3
  import { formatDuration } from "../../../utils/duration.js";
3
4
  import { formatProviderNativeBody, formatProviderNativeSummary } from "../../provider-native-rendering.js";
4
5
  import { theme } from "../theme/theme.js";
@@ -132,7 +133,12 @@ export function createAssistantRenderDescriptors(message, options) {
132
133
  // wording so the retry engine can read it; the transcript gets the
133
134
  // plain-language version, without the recovery advice a retry still in
134
135
  // flight would contradict, and never the internal replay marker.
135
- const described = describeProviderFailureForUser(message.errorMessage);
136
+ const described = message.errorCause?.type === "usage_limit" && message.errorCause.scope === "account"
137
+ ? message.errorMessage
138
+ : describeProviderFailureForUser(message.errorMessage, {
139
+ model: `${message.provider}/${message.model}`,
140
+ usageLimit: usageLimitScope(message),
141
+ });
136
142
  addError(described ?? `Error: ${stripTurnRetrySuppressionPrefix(message.errorMessage ?? "") || "Unknown error"}`);
137
143
  break;
138
144
  }
@@ -0,0 +1,6 @@
1
+ import type { EditorComponent } from "@earendil-works/pi-tui";
2
+ /** Extension-supplied text updates the composer but is not terminal input. */
3
+ export declare function insertProgrammaticComposerInput(editor: EditorComponent, data: string): void;
4
+ /** Observe user edits, not setText calls that restore drafts during resume/reload. */
5
+ export declare function observeComposerActivity(editor: EditorComponent, onActivity: () => void): void;
6
+ //# sourceMappingURL=composer-user-activity.d.ts.map
@@ -0,0 +1,30 @@
1
+ const unobservedInput = new WeakMap();
2
+ /** Extension-supplied text updates the composer but is not terminal input. */
3
+ export function insertProgrammaticComposerInput(editor, data) {
4
+ const handleInput = unobservedInput.get(editor) ?? editor.handleInput.bind(editor);
5
+ handleInput(data);
6
+ }
7
+ /** Observe user edits, not setText calls that restore drafts during resume/reload. */
8
+ export function observeComposerActivity(editor, onActivity) {
9
+ const handleInput = editor.handleInput.bind(editor);
10
+ unobservedInput.set(editor, handleInput);
11
+ editor.handleInput = (data) => {
12
+ const previousText = editor.getText();
13
+ const onSubmit = editor.onSubmit;
14
+ let submitted = false;
15
+ editor.onSubmit = (...args) => {
16
+ submitted = true;
17
+ onSubmit?.(...args);
18
+ };
19
+ try {
20
+ handleInput(data);
21
+ // Submission clears the editor and is handled by prompt admission instead.
22
+ if (!submitted && editor.getText() !== previousText)
23
+ onActivity();
24
+ }
25
+ finally {
26
+ editor.onSubmit = onSubmit;
27
+ }
28
+ };
29
+ }
30
+ //# sourceMappingURL=composer-user-activity.js.map
@@ -1,6 +1,6 @@
1
1
  import { CustomEditor } from "../components/custom-editor.js";
2
2
  import { WorkingStatusIndicator } from "../components/status-indicator.js";
3
- import { theme } from "../theme/theme.js";
3
+ import { getReservedKeywordHighlight, theme } from "../theme/theme.js";
4
4
  import { getGrokChromeTokens } from "./chrome-tokens.js";
5
5
  import { GrokFooter } from "./footer.js";
6
6
  import { GrokInputCard } from "./input-card.js";
@@ -60,6 +60,7 @@ export class GrokChrome {
60
60
  const tokens = getGrokChromeTokens();
61
61
  return {
62
62
  borderColor: tokens.inputBorder,
63
+ keywordHighlight: getReservedKeywordHighlight(),
63
64
  selectList: {
64
65
  selectedPrefix: (text) => theme.fg("accent", text),
65
66
  selectedText: (text) => tokens.primaryText(text),
@@ -24,6 +24,7 @@ import { AssistantEditError, assistantTextEquals } from "../../core/edited-assis
24
24
  import { askUserAnswerDisplayText, formatUserMessage } from "../../core/extensions/builtin/ask-user/format.js";
25
25
  import { askUserRenderers } from "../../core/extensions/builtin/ask-user/render.js";
26
26
  import { buildNoticeBox } from "../../core/extensions/notice/index.js";
27
+ import { emitUserActivity } from "../../core/extensions/user-activity.js";
27
28
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
28
29
  import { sessionHolderWarning } from "../../core/foreign-session-holders.js";
29
30
  import { appendUncaughtCrashLog, appendUnhandledRejectionLog } from "../../core/hidden-stdout-log.js";
@@ -106,6 +107,7 @@ import { TreeSelectorComponent } from "./components/tree-selector.js";
106
107
  import { TrustSelectorComponent } from "./components/trust-selector.js";
107
108
  import { UserMessageComponent } from "./components/user-message.js";
108
109
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
110
+ import { insertProgrammaticComposerInput, observeComposerActivity } from "./composer-user-activity.js";
109
111
  import { expandEditorSubmission, expandSubmittedText, transferEditorContent } from "./editor-paste-transfer.js";
110
112
  import { formatExtensionErrorHeadline, sanitizeTuiErrorMessage } from "./extension-error-format.js";
111
113
  import { editFileInExternalEditor, editInExternalEditor } from "./external-editor.js";
@@ -470,7 +472,7 @@ export class OptimisticUserEchoController {
470
472
  const IMAGE_MARKER_PATTERN = /\[Image #([1-9]\d*)\]/g;
471
473
  /**
472
474
  * Attach a clipboard bitmap as an in-memory image behind an atomic
473
- * `[Image #N]` marker. Returns false when nothing was attached, so the caller
475
+ * `[Image #N]` marker. Returns false when the paste was unhandled, so the caller
474
476
  * falls through to the plain-text clipboard path.
475
477
  *
476
478
  * The bytes deliberately never touch the filesystem: writing a temp file and
@@ -496,10 +498,10 @@ async function attachClipboardImage(deps, image) {
496
498
  if (deps.isCompacting()) {
497
499
  // The compaction queue carries text only; an attachment queued behind
498
500
  // compaction would be silently lost at delivery, so drop it here with a
499
- // visible status. Consume the paste (true) - the clipboard holds a
501
+ // visible status. Consume the paste as dropped - the clipboard holds a
500
502
  // bitmap, so the plain-text fallback has nothing useful to insert.
501
503
  deps.showStatus("Image paste dropped: messages sent during compaction cannot carry images - paste again after compaction finishes");
502
- return true;
504
+ return "dropped";
503
505
  }
504
506
  const processed = await processImage(image.bytes, image.mimeType, {
505
507
  autoResizeImages: deps.settings.getImageAutoResize(),
@@ -521,7 +523,7 @@ async function attachClipboardImage(deps, image) {
521
523
  mimeType: processed.mimeType,
522
524
  });
523
525
  deps.requestRender();
524
- return true;
526
+ return "attached";
525
527
  }
526
528
  export class InteractiveMode {
527
529
  static confirmsRefusedUnknownCommand(host, text) {
@@ -764,6 +766,7 @@ export class InteractiveMode {
764
766
  });
765
767
  }
766
768
  this.editor = this.defaultEditor;
769
+ observeComposerActivity(this.defaultEditor, () => emitUserActivity(this.session, "draft"));
767
770
  this.editorContainer = new Container();
768
771
  this.editorContainer.addChild(this.editor);
769
772
  this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
@@ -2828,7 +2831,7 @@ export class InteractiveMode {
2828
2831
  this.applyTerminalTitle();
2829
2832
  },
2830
2833
  custom: (factory, options) => this.showExtensionCustom(factory, options),
2831
- pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
2834
+ pasteToEditor: (text) => insertProgrammaticComposerInput(this.editor, `\x1b[200~${text}\x1b[201~`),
2832
2835
  setEditorText: (text) => this.editor.setText(text),
2833
2836
  getEditorText: () => this.getExpandedEditorText(),
2834
2837
  editor: (title, prefill) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionEditor(title, prefill)),
@@ -3377,6 +3380,7 @@ export class InteractiveMode {
3377
3380
  this.defaultEditor.onSubmit?.(expandSubmittedText(newEditor, text));
3378
3381
  };
3379
3382
  newEditor.onChange = this.defaultEditor.onChange;
3383
+ observeComposerActivity(newEditor, () => emitUserActivity(this.session, "draft"));
3380
3384
  // Copy text (and any collapsed paste/image markers) from previous editor.
3381
3385
  // Image payloads live on this instance, so they must be dropped when the
3382
3386
  // destination cannot own their markers.
@@ -3669,6 +3673,7 @@ export class InteractiveMode {
3669
3673
  const leadingSpace = characterBeforeCursor && !/\s/.test(characterBeforeCursor) ? " " : "";
3670
3674
  const trailingSpace = characterAfterCursor && !/\s/.test(characterAfterCursor) ? " " : "";
3671
3675
  this.editor.insertTextAtCursor?.(`${leadingSpace}${paths}${trailingSpace}`);
3676
+ emitUserActivity(this.session, "draft");
3672
3677
  this.ui.requestRender();
3673
3678
  return;
3674
3679
  }
@@ -3682,11 +3687,15 @@ export class InteractiveMode {
3682
3687
  showStatus: (message) => this.showStatus(message),
3683
3688
  requestRender: () => this.ui.requestRender(),
3684
3689
  }, image));
3685
- if (attached)
3690
+ if (attached) {
3691
+ if (attached === "attached")
3692
+ emitUserActivity(this.session, "draft");
3686
3693
  return;
3694
+ }
3687
3695
  const text = await readClipboardText();
3688
3696
  if (text) {
3689
3697
  this.editor.insertTextAtCursor?.(text);
3698
+ emitUserActivity(this.session, "draft");
3690
3699
  this.ui.requestRender();
3691
3700
  }
3692
3701
  }
@@ -12,6 +12,7 @@
12
12
  import { createHash } from "node:crypto";
13
13
  import { rmSync } from "node:fs";
14
14
  import { chmod, lstat, mkdir, realpath, rm } from "node:fs/promises";
15
+ import { tmpdir } from "node:os";
15
16
  import { dirname, join } from "node:path";
16
17
  import { engineBuildIdentity } from "../../core/engine-build-identity.js";
17
18
  import { createDaemonDirectories, createHostDaemonPaths } from "../rpc/host-daemon-paths.js";
@@ -35,7 +36,13 @@ export async function resolveTuiSocket(agentDir, instanceId) {
35
36
  await makePrivateDirectory(dirname(primary));
36
37
  return primary;
37
38
  }
38
- const root = join("/tmp", `senpi-rpc-${createHash("sha256").update(agentDir).digest("hex").slice(0, 8)}`);
39
+ const fallbackName = `senpi-rpc-${createHash("sha256").update(agentDir).digest("hex").slice(0, 8)}`;
40
+ const scopedRoot = join(tmpdir(), fallbackName);
41
+ // Test launchers supply a short owned TMPDIR. A normal user's long macOS temp path
42
+ // still needs the original short system fallback to fit sun_path.
43
+ const root = Buffer.byteLength(join(scopedRoot, "tui", name)) <= MAX_SOCKET_PATH_BYTES
44
+ ? scopedRoot
45
+ : join("/tmp", fallbackName);
39
46
  const tuiRoot = join(root, "tui");
40
47
  await makePrivateDirectory(tuiRoot);
41
48
  for (const directory of [root, tuiRoot])
@@ -0,0 +1,11 @@
1
+ import { type Color, type EditorKeywordHighlight, type TerminalColorMode } from "@earendil-works/pi-tui";
2
+ import type { ThemeAppearance } from "./theme.ts";
3
+ export declare function reservedKeywordColor(appearance: ThemeAppearance, index: number, length: number): Color;
4
+ export interface ReservedKeywordHighlightSource {
5
+ readonly appearance: () => ThemeAppearance;
6
+ readonly colorMode: () => TerminalColorMode;
7
+ readonly env?: NodeJS.ProcessEnv;
8
+ }
9
+ /** `NO_COLOR` (any non-empty value) turns the decoration off entirely. */
10
+ export declare function createReservedKeywordHighlight(source: ReservedKeywordHighlightSource): EditorKeywordHighlight | undefined;
11
+ //# sourceMappingURL=reserved-keyword-rainbow.d.ts.map
@@ -0,0 +1,50 @@
1
+ import { foregroundAnsi, mixColors, parseColor, } from "@earendil-works/pi-tui";
2
+ import { findReservedKeywordRuns } from "../../../core/reserved-keywords.js";
3
+ function spectrum(hexes) {
4
+ const positions = [0, 0.18, 0.36, 0.54, 0.72, 0.9, 1];
5
+ return hexes.map((hex, index) => ({ at: positions[index] ?? 1, color: parseColor(hex) }));
6
+ }
7
+ // The desktop composer's `.composer-reserved-keyword` stops: 700 shades on light surfaces,
8
+ // 400 shades on dark ones, so every stop clears 4.5:1 as text.
9
+ const SPECTRUM = {
10
+ light: spectrum(["#b91c1c", "#c2410c", "#15803d", "#0f766e", "#1d4ed8", "#be185d", "#b91c1c"]),
11
+ dark: spectrum(["#f87171", "#fbbf24", "#4ade80", "#2dd4bf", "#60a5fa", "#f472b6", "#f87171"]),
12
+ };
13
+ // A run spans red through pink; the final stop wraps back to red and is left out so the
14
+ // first and last characters of a keyword never share a colour.
15
+ const SPAN_END = 0.9;
16
+ export function reservedKeywordColor(appearance, index, length) {
17
+ const stops = SPECTRUM[appearance];
18
+ const at = length <= 1 ? 0 : (Math.min(Math.max(index, 0), length - 1) / (length - 1)) * SPAN_END;
19
+ for (let stop = 1; stop < stops.length; stop++) {
20
+ const left = stops[stop - 1];
21
+ const right = stops[stop];
22
+ if (left === undefined || right === undefined || at > right.at)
23
+ continue;
24
+ const amount = right.at === left.at ? 0 : (at - left.at) / (right.at - left.at);
25
+ return mixColors(left.color, right.color, amount, "srgb");
26
+ }
27
+ return stops[0]?.color ?? parseColor("#ffffff");
28
+ }
29
+ /** `NO_COLOR` (any non-empty value) turns the decoration off entirely. */
30
+ export function createReservedKeywordHighlight(source) {
31
+ const noColor = (source.env ?? process.env).NO_COLOR;
32
+ if (noColor !== undefined && noColor !== "")
33
+ return undefined;
34
+ const cache = new Map();
35
+ return {
36
+ find: findReservedKeywordRuns,
37
+ style: (text, index, length) => {
38
+ const appearance = source.appearance();
39
+ const mode = source.colorMode();
40
+ const key = `${appearance}:${mode}:${index}:${length}`;
41
+ let open = cache.get(key);
42
+ if (open === undefined) {
43
+ open = foregroundAnsi(reservedKeywordColor(appearance, index, length), mode);
44
+ cache.set(key, open);
45
+ }
46
+ return `${open}${text}\x1b[39m`;
47
+ },
48
+ };
49
+ }
50
+ //# sourceMappingURL=reserved-keyword-rainbow.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
- import { type Color, type EditorTheme, type MarkdownTheme, type SelectListTheme, type SettingsListTheme, type TerminalColorMode, type TerminalColors, type TextAttributes } from "@earendil-works/pi-tui";
2
+ import { type Color, type EditorKeywordHighlight, type EditorTheme, type MarkdownTheme, type SelectListTheme, type SettingsListTheme, type TerminalColorMode, type TerminalColors, type TextAttributes } from "@earendil-works/pi-tui";
3
3
  import type { SourceInfo } from "../../../core/source-info.ts";
4
4
  /** Name of the built-in theme generated from the terminal's colors (`"system"`), used when no theme is set. */
5
5
  export { SYSTEM_THEME_NAME } from "./system-theme.ts";
@@ -147,5 +147,7 @@ export declare function getLanguageFromPath(filePath: string): string | undefine
147
147
  export declare function getMarkdownTheme(): MarkdownTheme;
148
148
  export declare function getSelectListTheme(): SelectListTheme;
149
149
  export declare function getEditorTheme(): EditorTheme;
150
+ /** Rainbow for OmO's reserved prompt keywords; it follows the active theme's appearance and colour mode. */
151
+ export declare function getReservedKeywordHighlight(): EditorKeywordHighlight | undefined;
150
152
  export declare function getSettingsListTheme(): SettingsListTheme;
151
153
  //# sourceMappingURL=theme.d.ts.map
@@ -5,6 +5,7 @@ import chalk from "chalk";
5
5
  import { getCustomThemesDir, getThemesDir } from "../../../config.js";
6
6
  import { closeWatcher, watchWithErrorHandler } from "../../../utils/fs-watch.js";
7
7
  import { highlight, supportsLanguage } from "../../../utils/syntax-highlight.js";
8
+ import { createReservedKeywordHighlight } from "./reserved-keyword-rainbow.js";
8
9
  import { generateSystemThemeColors, SYSTEM_THEME_NAME, terminalAppearance } from "./system-theme.js";
9
10
  /** Name of the built-in theme generated from the terminal's colors (`"system"`), used when no theme is set. */
10
11
  export { SYSTEM_THEME_NAME } from "./system-theme.js";
@@ -888,9 +889,17 @@ export function getEditorTheme() {
888
889
  return {
889
890
  borderColor: (text) => theme.fg("borderMuted", text),
890
891
  mention: (text) => theme.fg("skillMention", `\x1b[1m${text}\x1b[22m`),
892
+ keywordHighlight: getReservedKeywordHighlight(),
891
893
  selectList: getSelectListTheme(),
892
894
  };
893
895
  }
896
+ /** Rainbow for OmO's reserved prompt keywords; it follows the active theme's appearance and colour mode. */
897
+ export function getReservedKeywordHighlight() {
898
+ return createReservedKeywordHighlight({
899
+ appearance: () => theme.appearance,
900
+ colorMode: () => theme.getColorMode(),
901
+ });
902
+ }
894
903
  export function getSettingsListTheme() {
895
904
  return {
896
905
  label: (text, selected) => (selected ? theme.fg("accent", text) : text),
@@ -8,7 +8,7 @@
8
8
  import { describeProviderFailureForUser, stripTurnRetrySuppressionPrefix, } from "@earendil-works/pi-ai";
9
9
  import { REQUIRED_COMPACTION_ERROR_MESSAGE } from "../core/agent-session.js";
10
10
  import { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from "../core/output-guard.js";
11
- import { usageLimitCause } from "../core/retry-fallback/usage-limit.js";
11
+ import { usageLimitCause, usageLimitScope } from "../core/retry-fallback/usage-limit.js";
12
12
  import { killTrackedDetachedChildren } from "../utils/shell.js";
13
13
  import { toJsonEvent } from "./json-event.js";
14
14
  import { formatProviderNativeBody, formatProviderNativeSummary } from "./provider-native-rendering.js";
@@ -157,7 +157,12 @@ export async function runPrintMode(runtimeHost, options) {
157
157
  if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
158
158
  // A provider-stream stall or transport drop keeps the classifier wording
159
159
  // on the message; stderr gets the plain-language version instead.
160
- const described = describeProviderFailureForUser(assistantMsg.errorMessage);
160
+ const described = assistantMsg.errorCause?.type === "usage_limit" && assistantMsg.errorCause.scope === "account"
161
+ ? assistantMsg.errorMessage
162
+ : describeProviderFailureForUser(assistantMsg.errorMessage, {
163
+ model: `${assistantMsg.provider}/${assistantMsg.model}`,
164
+ usageLimit: usageLimitScope(assistantMsg),
165
+ });
161
166
  console.error(described ??
162
167
  (stripTurnRetrySuppressionPrefix(assistantMsg.errorMessage ?? "") ||
163
168
  `Request ${assistantMsg.stopReason}`));
@@ -26,13 +26,15 @@ import { emitProviderAccountsChanged, subscribeProviderAccountEvents, } from "..
26
26
  import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "../../core/extensions/builtin/anthropic-subscription/account-management.js";
27
27
  import { isMcpControlInventoryChanged, MCP_CONTROL_INVENTORY_CHANGED_EVENT, MCP_CONTROL_INVENTORY_REQUEST_EVENT, } from "../../core/extensions/builtin/mcp/control-inventory.js";
28
28
  import { applyFastMode, resolveServiceTierMemoryModel, } from "../../core/extensions/builtin/service-tier.js";
29
+ import { ExtensionRpcRequestNotFoundError } from "../../core/extensions/runner.js";
30
+ import { describeUsageLimitEntry, describeUsageLimitMessage } from "../../core/retry-fallback/usage-limit.js";
29
31
  import { theme } from "../interactive/theme/theme.js";
30
32
  import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.js";
31
33
  import { ClientAdmissions } from "./client-admissions.js";
32
34
  import { handleClientInput } from "./client-input-handler.js";
33
35
  import { ClientMessageEvents } from "./client-message-events.js";
34
36
  import { ConnectionQuestionBridge, degradeQuestion, sessionQuestionBridges } from "./connection-question-bridge.js";
35
- import { AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EXTENSION_EVENTS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, QUESTION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, } from "./custom-capability.js";
37
+ import { AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, QUESTION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, } from "./custom-capability.js";
36
38
  import { createRpcEventOutputBuffer } from "./event-output-buffer.js";
37
39
  import { settleExtensionUiResponse } from "./extension-ui-response.js";
38
40
  import { HostSessionControl } from "./host-session-control.js";
@@ -42,7 +44,7 @@ import { protocolIdentity } from "./protocol-identity.js";
42
44
  import { buildRpcCommandsForSession, createCommandsChangedEvent, rpcCommandListDigest } from "./rpc-command-surface.js";
43
45
  import { rpcCommandPayloadError, rpcCommandShapeError, rpcMessageLengthError, sessionRetryFallbackError, } from "./rpc-input-validation.js";
44
46
  import { buildRpcSessionState } from "./rpc-session-state.js";
45
- import { RPC_ERROR_ENTRY_NOT_FOUND, RPC_ERROR_MEDIA_NOT_FOUND, RPC_ERROR_MODEL_NOT_FOUND } from "./rpc-types.js";
47
+ import { RPC_ERROR_ENTRY_NOT_FOUND, RPC_ERROR_EVAL_CELL_NOT_FOUND, RPC_ERROR_EVAL_CELLS_UNAVAILABLE, RPC_ERROR_INVALID_EVAL_CELL_REQUEST, RPC_ERROR_MEDIA_NOT_FOUND, RPC_ERROR_MODEL_NOT_FOUND, } from "./rpc-types.js";
46
48
  import { availableModelsData, interruptRunningTurn, unsupportedThinkingLevel } from "./session-control-actions-data.js";
47
49
  import { SessionExtensionUiRequests } from "./session-extension-ui-requests.js";
48
50
  export { buildRpcSessionState } from "./rpc-session-state.js";
@@ -738,6 +740,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
738
740
  MEDIA_PLACEHOLDERS_CAPABILITY,
739
741
  DURABLE_CLIENT_MESSAGE_ID_CAPABILITY,
740
742
  CONTINUE_FROM_LEAF_CAPABILITY,
743
+ EVAL_CELLS_CAPABILITY,
741
744
  ...(options.retryFallbackCommand ? [RETRY_FALLBACK_COMMAND_CAPABILITY] : []),
742
745
  // Client flags cannot advertise a host guard this classic binding does not run.
743
746
  ...(options.capabilities ?? []).filter((capability) => capability !== SESSION_HELD_CAPABILITY),
@@ -1249,11 +1252,22 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1249
1252
  }
1250
1253
  entries = entries.slice(sinceIndex + 1);
1251
1254
  }
1252
- return success(id, "get_entries", { entries, leafId: sessionManager.getLeafId() });
1255
+ return success(id, "get_entries", {
1256
+ entries: entries.map(describeUsageLimitEntry),
1257
+ leafId: sessionManager.getLeafId(),
1258
+ });
1253
1259
  }
1254
1260
  case "get_tree": {
1255
1261
  const sessionManager = session.sessionManager;
1256
- return success(id, "get_tree", { tree: sessionManager.getTree(), leafId: sessionManager.getLeafId() });
1262
+ const tree = sessionManager.getTree();
1263
+ // getTree returns defensive nodes; keep the persisted entries diagnostic-bearing.
1264
+ const nodes = [...tree];
1265
+ for (const node of nodes) {
1266
+ node.entry = describeUsageLimitEntry(node.entry);
1267
+ for (const child of node.children)
1268
+ nodes.push(child);
1269
+ }
1270
+ return success(id, "get_tree", { tree, leafId: sessionManager.getLeafId() });
1257
1271
  }
1258
1272
  case "get_last_assistant_text": {
1259
1273
  const text = session.getLastAssistantText();
@@ -1277,7 +1291,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1277
1291
  // Messages
1278
1292
  // =================================================================
1279
1293
  case "get_messages": {
1280
- return success(id, "get_messages", { messages: session.messages });
1294
+ return success(id, "get_messages", {
1295
+ messages: session.messages.map((message) => message.role === "assistant" ? describeUsageLimitMessage(message) : message),
1296
+ });
1281
1297
  }
1282
1298
  // On-demand fetch of a media block a `media_placeholders` client received as
1283
1299
  // an `image_ref` stub. Durable session entries first (they survive the live
@@ -1302,13 +1318,29 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1302
1318
  const inventory = recordLoadedSurfaces(true);
1303
1319
  return success(id, "get_loaded_surfaces", inventory.data);
1304
1320
  }
1305
- case "extension_request": {
1306
- const name = command.name.trim();
1307
- if (name.length === 0) {
1308
- return error(id, "extension_request", "Extension RPC request name cannot be empty");
1321
+ case "extension_request":
1322
+ case "eval_cells.list":
1323
+ case "eval_cells.peek":
1324
+ case "eval_cells.stop": {
1325
+ const name = command.type === "extension_request" ? command.name.trim() : command.type;
1326
+ if (name.length === 0)
1327
+ return error(id, command.type, "Extension RPC request name cannot be empty");
1328
+ try {
1329
+ const data = await session.extensionRunner.requestRpc(name, command.type === "extension_request" ? command.data : command);
1330
+ return success(id, command.type, data);
1331
+ }
1332
+ catch (cause) {
1333
+ if (cause instanceof ExtensionRpcRequestNotFoundError &&
1334
+ (name === "eval_cells.list" || name === "eval_cells.peek" || name === "eval_cells.stop")) {
1335
+ return error(id, command.type, "Eval cell controls are unavailable in this session", RPC_ERROR_EVAL_CELLS_UNAVAILABLE);
1336
+ }
1337
+ if (cause instanceof Error &&
1338
+ "code" in cause &&
1339
+ (cause.code === RPC_ERROR_EVAL_CELL_NOT_FOUND || cause.code === RPC_ERROR_INVALID_EVAL_CELL_REQUEST)) {
1340
+ return error(id, command.type, cause.message, cause.code);
1341
+ }
1342
+ throw cause;
1309
1343
  }
1310
- const data = await session.extensionRunner.requestRpc(name, command.data);
1311
- return success(id, "extension_request", data);
1312
1344
  }
1313
1345
  // =================================================================
1314
1346
  // Auth (task 13)
@@ -17,6 +17,8 @@ import type { RpcExtensionUIRequest } from "./rpc-types.ts";
17
17
  /** The capability string a client sends in its handshake to opt into the notice. */
18
18
  export declare const CUSTOM_UNSUPPORTED_CAPABILITY = "custom_unsupported";
19
19
  export declare const EXTENSION_EVENTS_CAPABILITY = "extension_events";
20
+ /** Host protocol support; a session without codemode returns `eval_cells_unavailable`. */
21
+ export declare const EVAL_CELLS_CAPABILITY = "eval_cells";
20
22
  export declare const AUTO_TITLE_SESSIONS_CAPABILITY = "auto_title_sessions";
21
23
  /** Host capability: the `continue_from_leaf` command starts a turn with no new prompt (#1930). */
22
24
  export declare const CONTINUE_FROM_LEAF_CAPABILITY = "continue_from_leaf";
@@ -17,6 +17,8 @@ import * as crypto from "node:crypto";
17
17
  /** The capability string a client sends in its handshake to opt into the notice. */
18
18
  export const CUSTOM_UNSUPPORTED_CAPABILITY = "custom_unsupported";
19
19
  export const EXTENSION_EVENTS_CAPABILITY = "extension_events";
20
+ /** Host protocol support; a session without codemode returns `eval_cells_unavailable`. */
21
+ export const EVAL_CELLS_CAPABILITY = "eval_cells";
20
22
  export const AUTO_TITLE_SESSIONS_CAPABILITY = "auto_title_sessions";
21
23
  /** Host capability: the `continue_from_leaf` command starts a turn with no new prompt (#1930). */
22
24
  export const CONTINUE_FROM_LEAF_CAPABILITY = "continue_from_leaf";
@@ -0,0 +1,9 @@
1
+ import type { Socket } from "node:net";
2
+ import { type HostAdmission } from "./host-admission.ts";
3
+ /**
4
+ * Requests are size-bounded; each direction awaits downstream socket backpressure.
5
+ * Opens remain concurrent with control commands. Stop refuses every pending open,
6
+ * while other commands retain their wire order and child-owned semantics.
7
+ */
8
+ export declare function proxyWithAdmission(client: Socket, internal: Socket | undefined, admission: HostAdmission): void;
9
+ //# sourceMappingURL=host-admission-proxy.d.ts.map