@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.7

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 (223) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/cli.js +1945 -1386
  3. package/dist/types/advisor/advise-tool.d.ts +22 -19
  4. package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
  5. package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
  6. package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
  7. package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
  8. package/dist/types/cli/ttsr-cli.d.ts +39 -0
  9. package/dist/types/commands/ttsr.d.ts +57 -0
  10. package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
  11. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
  12. package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
  13. package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
  14. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
  15. package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
  16. package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
  17. package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
  18. package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
  19. package/dist/types/commit/changelog/generate.d.ts +12 -13
  20. package/dist/types/commit/shared-llm.d.ts +10 -37
  21. package/dist/types/config/config-file.d.ts +4 -4
  22. package/dist/types/config/keybindings.d.ts +5 -0
  23. package/dist/types/config/models-config-schema.d.ts +625 -990
  24. package/dist/types/config/models-config.d.ts +229 -217
  25. package/dist/types/config/settings-schema.d.ts +53 -23
  26. package/dist/types/edit/hashline/params.d.ts +7 -11
  27. package/dist/types/edit/index.d.ts +2 -1
  28. package/dist/types/edit/modes/apply-patch.d.ts +4 -5
  29. package/dist/types/edit/modes/patch.d.ts +15 -24
  30. package/dist/types/edit/modes/replace.d.ts +16 -17
  31. package/dist/types/eval/js/index.d.ts +1 -0
  32. package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
  33. package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
  34. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  35. package/dist/types/extensibility/hooks/types.d.ts +7 -4
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
  38. package/dist/types/extensibility/typebox.d.ts +80 -58
  39. package/dist/types/goals/tools/goal-tool.d.ts +11 -24
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/lsp/index.d.ts +11 -26
  42. package/dist/types/lsp/types.d.ts +12 -28
  43. package/dist/types/mcp/client.d.ts +8 -0
  44. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  46. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  47. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +3 -0
  49. package/dist/types/modes/setup-wizard/index.d.ts +1 -0
  50. package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
  51. package/dist/types/modes/theme/theme.d.ts +1 -1
  52. package/dist/types/modes/types.d.ts +3 -0
  53. package/dist/types/sdk.d.ts +5 -0
  54. package/dist/types/session/agent-session.d.ts +4 -0
  55. package/dist/types/startup-splash.d.ts +12 -0
  56. package/dist/types/task/types.d.ts +47 -48
  57. package/dist/types/tools/ask.d.ts +26 -27
  58. package/dist/types/tools/ast-edit.d.ts +17 -17
  59. package/dist/types/tools/ast-grep.d.ts +12 -13
  60. package/dist/types/tools/bash.d.ts +20 -17
  61. package/dist/types/tools/browser.d.ts +46 -71
  62. package/dist/types/tools/checkpoint.d.ts +14 -15
  63. package/dist/types/tools/debug.d.ts +82 -145
  64. package/dist/types/tools/eval.d.ts +30 -40
  65. package/dist/types/tools/find.d.ts +17 -18
  66. package/dist/types/tools/gh.d.ts +49 -78
  67. package/dist/types/tools/image-gen.d.ts +20 -36
  68. package/dist/types/tools/inspect-image.d.ts +10 -11
  69. package/dist/types/tools/irc.d.ts +22 -33
  70. package/dist/types/tools/job.d.ts +11 -12
  71. package/dist/types/tools/learn.d.ts +21 -28
  72. package/dist/types/tools/manage-skill.d.ts +13 -22
  73. package/dist/types/tools/memory-edit.d.ts +15 -24
  74. package/dist/types/tools/memory-recall.d.ts +7 -8
  75. package/dist/types/tools/memory-reflect.d.ts +9 -10
  76. package/dist/types/tools/memory-retain.d.ts +13 -14
  77. package/dist/types/tools/read.d.ts +7 -8
  78. package/dist/types/tools/resolve.d.ts +11 -18
  79. package/dist/types/tools/review.d.ts +9 -15
  80. package/dist/types/tools/search-tool-bm25.d.ts +9 -10
  81. package/dist/types/tools/search.d.ts +16 -17
  82. package/dist/types/tools/ssh.d.ts +14 -15
  83. package/dist/types/tools/todo.d.ts +27 -43
  84. package/dist/types/tools/tts.d.ts +8 -9
  85. package/dist/types/tools/write.d.ts +9 -10
  86. package/dist/types/tui/index.d.ts +1 -0
  87. package/dist/types/tui/width-aware-text.d.ts +23 -0
  88. package/dist/types/utils/markit.d.ts +10 -1
  89. package/dist/types/web/search/index.d.ts +17 -28
  90. package/dist/types/web/search/providers/perplexity.d.ts +0 -2
  91. package/dist/types/web/search/types.d.ts +32 -26
  92. package/package.json +14 -13
  93. package/scripts/omp +1 -1
  94. package/src/advisor/__tests__/advisor.test.ts +44 -1
  95. package/src/advisor/advise-tool.ts +34 -11
  96. package/src/autoresearch/tools/init-experiment.ts +13 -16
  97. package/src/autoresearch/tools/log-experiment.ts +15 -18
  98. package/src/autoresearch/tools/run-experiment.ts +3 -3
  99. package/src/autoresearch/tools/update-notes.ts +4 -4
  100. package/src/cli/ttsr-cli.ts +995 -0
  101. package/src/cli-commands.ts +1 -0
  102. package/src/cli.ts +7 -1
  103. package/src/commands/ttsr.ts +125 -0
  104. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  105. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  106. package/src/commit/agentic/tools/git-hunk.ts +7 -5
  107. package/src/commit/agentic/tools/git-overview.ts +4 -4
  108. package/src/commit/agentic/tools/propose-changelog.ts +18 -15
  109. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  110. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  111. package/src/commit/agentic/tools/schemas.ts +8 -20
  112. package/src/commit/agentic/tools/split-commit.ts +19 -23
  113. package/src/commit/analysis/summary.ts +7 -5
  114. package/src/commit/changelog/generate.ts +15 -11
  115. package/src/commit/shared-llm.ts +17 -24
  116. package/src/config/config-file.ts +13 -15
  117. package/src/config/keybindings.ts +6 -0
  118. package/src/config/models-config-schema.ts +206 -179
  119. package/src/config/settings-schema.ts +34 -0
  120. package/src/discovery/builtin-rules/index.ts +2 -0
  121. package/src/discovery/builtin-rules/ts-import-type.md +2 -2
  122. package/src/discovery/builtin-rules/ts-no-any.md +11 -2
  123. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  124. package/src/edit/hashline/params.ts +12 -11
  125. package/src/edit/index.ts +5 -4
  126. package/src/edit/modes/apply-patch.ts +4 -4
  127. package/src/edit/modes/patch.ts +15 -18
  128. package/src/edit/modes/replace.ts +13 -17
  129. package/src/edit/renderer.ts +0 -1
  130. package/src/eval/agent-bridge.ts +11 -13
  131. package/src/eval/completion-bridge.ts +25 -17
  132. package/src/eval/js/context-manager.ts +17 -2
  133. package/src/eval/js/index.ts +1 -1
  134. package/src/eval/py/executor.ts +2 -2
  135. package/src/extensibility/custom-commands/loader.ts +5 -3
  136. package/src/extensibility/custom-commands/types.ts +6 -3
  137. package/src/extensibility/custom-tools/loader.ts +4 -2
  138. package/src/extensibility/custom-tools/types.ts +8 -5
  139. package/src/extensibility/extensions/loader.ts +4 -2
  140. package/src/extensibility/extensions/types.ts +6 -3
  141. package/src/extensibility/hooks/loader.ts +5 -2
  142. package/src/extensibility/hooks/types.ts +7 -4
  143. package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
  144. package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
  145. package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
  146. package/src/extensibility/tool-proxy.ts +4 -1
  147. package/src/extensibility/typebox.ts +778 -251
  148. package/src/goals/guided-setup.ts +12 -3
  149. package/src/goals/tools/goal-tool.ts +6 -6
  150. package/src/index.ts +2 -0
  151. package/src/internal-urls/docs-index.generated.ts +11 -9
  152. package/src/lsp/types.ts +13 -27
  153. package/src/main.ts +19 -18
  154. package/src/mcp/client.ts +38 -13
  155. package/src/mcp/render.ts +102 -89
  156. package/src/modes/components/agent-hub.ts +11 -4
  157. package/src/modes/components/btw-panel.ts +5 -1
  158. package/src/modes/components/custom-editor.ts +18 -0
  159. package/src/modes/components/status-line/component.ts +8 -1
  160. package/src/modes/components/tool-execution.ts +17 -10
  161. package/src/modes/controllers/btw-controller.ts +69 -1
  162. package/src/modes/controllers/input-controller.ts +29 -0
  163. package/src/modes/interactive-mode.ts +38 -8
  164. package/src/modes/setup-wizard/index.ts +1 -0
  165. package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
  166. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  167. package/src/modes/theme/theme.ts +133 -143
  168. package/src/modes/types.ts +3 -0
  169. package/src/modes/utils/context-usage.ts +9 -5
  170. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  171. package/src/prompts/system/system-prompt.md +1 -0
  172. package/src/sdk.ts +21 -4
  173. package/src/session/agent-session.ts +173 -33
  174. package/src/session/session-history-format.ts +11 -2
  175. package/src/session/snapcompact-inline.ts +1 -1
  176. package/src/slash-commands/builtin-registry.ts +3 -10
  177. package/src/startup-splash.ts +19 -0
  178. package/src/task/executor.ts +11 -6
  179. package/src/task/types.ts +44 -41
  180. package/src/tool-discovery/tool-index.ts +17 -4
  181. package/src/tools/ask.ts +14 -14
  182. package/src/tools/ast-edit.ts +17 -14
  183. package/src/tools/ast-grep.ts +10 -9
  184. package/src/tools/bash.ts +15 -10
  185. package/src/tools/browser/launch.ts +13 -0
  186. package/src/tools/browser.ts +26 -32
  187. package/src/tools/checkpoint.ts +7 -7
  188. package/src/tools/debug.ts +72 -69
  189. package/src/tools/eval.ts +18 -19
  190. package/src/tools/find.ts +20 -13
  191. package/src/tools/gh.ts +29 -49
  192. package/src/tools/image-gen.ts +27 -32
  193. package/src/tools/inspect-image.ts +8 -9
  194. package/src/tools/irc.ts +12 -12
  195. package/src/tools/job.ts +6 -6
  196. package/src/tools/learn.ts +11 -14
  197. package/src/tools/manage-skill.ts +19 -23
  198. package/src/tools/memory-edit.ts +8 -8
  199. package/src/tools/memory-recall.ts +4 -4
  200. package/src/tools/memory-reflect.ts +5 -5
  201. package/src/tools/memory-retain.ts +9 -11
  202. package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
  203. package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
  204. package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
  205. package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
  206. package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
  207. package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
  208. package/src/tools/read.ts +169 -13
  209. package/src/tools/report-tool-issue.ts +6 -6
  210. package/src/tools/resolve.ts +6 -6
  211. package/src/tools/review.ts +10 -12
  212. package/src/tools/search-tool-bm25.ts +5 -5
  213. package/src/tools/search.ts +20 -29
  214. package/src/tools/ssh.ts +8 -8
  215. package/src/tools/todo.ts +16 -19
  216. package/src/tools/tts.ts +16 -15
  217. package/src/tools/write.ts +5 -5
  218. package/src/tui/index.ts +1 -0
  219. package/src/tui/width-aware-text.ts +58 -0
  220. package/src/utils/markit.ts +17 -2
  221. package/src/web/search/index.ts +9 -9
  222. package/src/web/search/providers/perplexity.ts +373 -126
  223. package/src/web/search/types.ts +28 -48
@@ -1,73 +1,72 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
 
3
- const OpenRouterRoutingSchema = z.object({
4
- only: z.array(z.string()).optional(),
5
- order: z.array(z.string()).optional(),
3
+ const OpenRouterRoutingSchema = type({
4
+ "only?": "string[]",
5
+ "order?": "string[]",
6
6
  });
7
7
 
8
- const VercelGatewayRoutingSchema = z.object({
9
- only: z.array(z.string()).optional(),
10
- order: z.array(z.string()).optional(),
8
+ const VercelGatewayRoutingSchema = type({
9
+ "only?": "string[]",
10
+ "order?": "string[]",
11
11
  });
12
12
 
13
- const ReasoningEffortMapSchema = z.object({
14
- minimal: z.string().optional(),
15
- low: z.string().optional(),
16
- medium: z.string().optional(),
17
- high: z.string().optional(),
18
- xhigh: z.string().optional(),
13
+ const ReasoningEffortMapSchema = type({
14
+ "minimal?": "string",
15
+ "low?": "string",
16
+ "medium?": "string",
17
+ "high?": "string",
18
+ "xhigh?": "string",
19
19
  });
20
20
 
21
- const OpenAICompatFieldsSchema = z.object({
22
- supportsStore: z.boolean().optional(),
23
- supportsDeveloperRole: z.boolean().optional(),
24
- supportsMultipleSystemMessages: z.boolean().optional(),
25
- supportsReasoningEffort: z.boolean().optional(),
26
- reasoningEffortMap: ReasoningEffortMapSchema.optional(),
27
- maxTokensField: z.enum(["max_completion_tokens", "max_tokens"]).optional(),
28
- supportsUsageInStreaming: z.boolean().optional(),
29
- requiresToolResultName: z.boolean().optional(),
30
- requiresMistralToolIds: z.boolean().optional(),
31
- requiresAssistantAfterToolResult: z.boolean().optional(),
32
- requiresThinkingAsText: z.boolean().optional(),
33
- reasoningContentField: z.enum(["reasoning_content", "reasoning", "reasoning_text"]).optional(),
34
- requiresReasoningContentForToolCalls: z.boolean().optional(),
35
- allowsSyntheticReasoningContentForToolCalls: z.boolean().optional(),
36
- requiresAssistantContentForToolCalls: z.boolean().optional(),
37
- supportsToolChoice: z.boolean().optional(),
38
- supportsForcedToolChoice: z.boolean().optional(),
39
- disableReasoningOnForcedToolChoice: z.boolean().optional(),
40
- disableReasoningOnToolChoice: z.boolean().optional(),
41
- thinkingFormat: z.enum(["openai", "openrouter", "zai", "qwen", "qwen-chat-template"]).optional(),
42
- openRouterRouting: OpenRouterRoutingSchema.optional(),
43
- vercelGatewayRouting: VercelGatewayRoutingSchema.optional(),
44
- extraBody: z.record(z.string(), z.unknown()).optional(),
45
- cacheControlFormat: z.enum(["anthropic"]).optional(),
46
- supportsStrictMode: z.boolean().optional(),
47
- toolStrictMode: z.enum(["all_strict", "none"]).optional(),
48
- streamIdleTimeoutMs: z.number().nonnegative().optional(),
49
- supportsLongPromptCacheRetention: z.boolean().optional(),
50
- supportsReasoningParams: z.boolean().optional(),
51
- alwaysSendMaxTokens: z.boolean().optional(),
52
- strictResponsesPairing: z.boolean().optional(),
21
+ const OpenAICompatFields = {
22
+ "supportsStore?": "boolean",
23
+ "supportsDeveloperRole?": "boolean",
24
+ "supportsMultipleSystemMessages?": "boolean",
25
+ "supportsReasoningEffort?": "boolean",
26
+ "reasoningEffortMap?": ReasoningEffortMapSchema,
27
+ "maxTokensField?": '"max_completion_tokens" | "max_tokens"',
28
+ "supportsUsageInStreaming?": "boolean",
29
+ "requiresToolResultName?": "boolean",
30
+ "requiresMistralToolIds?": "boolean",
31
+ "requiresAssistantAfterToolResult?": "boolean",
32
+ "requiresThinkingAsText?": "boolean",
33
+ "reasoningContentField?": '"reasoning_content" | "reasoning" | "reasoning_text"',
34
+ "requiresReasoningContentForToolCalls?": "boolean",
35
+ "allowsSyntheticReasoningContentForToolCalls?": "boolean",
36
+ "requiresAssistantContentForToolCalls?": "boolean",
37
+ "supportsToolChoice?": "boolean",
38
+ "supportsForcedToolChoice?": "boolean",
39
+ "disableReasoningOnForcedToolChoice?": "boolean",
40
+ "disableReasoningOnToolChoice?": "boolean",
41
+ "thinkingFormat?": '"openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template"',
42
+ "openRouterRouting?": OpenRouterRoutingSchema,
43
+ "vercelGatewayRouting?": VercelGatewayRoutingSchema,
44
+ "extraBody?": { "[string]": "unknown" },
45
+ "cacheControlFormat?": '"anthropic"',
46
+ "supportsStrictMode?": "boolean",
47
+ "toolStrictMode?": '"all_strict" | "none"',
48
+ "streamIdleTimeoutMs?": "number >= 0",
49
+ "supportsLongPromptCacheRetention?": "boolean",
50
+ "supportsReasoningParams?": "boolean",
51
+ "alwaysSendMaxTokens?": "boolean",
52
+ "strictResponsesPairing?": "boolean",
53
53
  // anthropic-messages compat flags (same `compat` slot, per-api interpretation)
54
- requiresToolResultId: z.boolean().optional(),
55
- replayUnsignedThinking: z.boolean().optional(),
56
- });
54
+ "requiresToolResultId?": "boolean",
55
+ "replayUnsignedThinking?": "boolean",
56
+ } as const;
57
+
58
+ const OpenAICompatFieldsSchema = type(OpenAICompatFields);
57
59
 
58
- export const OpenAICompatSchema = OpenAICompatFieldsSchema.extend({
59
- whenThinking: OpenAICompatFieldsSchema.optional(),
60
+ export const OpenAICompatSchema = type({
61
+ ...OpenAICompatFields,
62
+ "whenThinking?": OpenAICompatFieldsSchema,
60
63
  });
61
64
 
62
- const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh"]);
65
+ const EffortSchema = type('"minimal" | "low" | "medium" | "high" | "xhigh"');
63
66
 
64
- const ThinkingControlModeSchema = z.enum([
65
- "effort",
66
- "budget",
67
- "google-level",
68
- "anthropic-adaptive",
69
- "anthropic-budget-effort",
70
- ]);
67
+ const ThinkingControlModeSchema = type(
68
+ '"effort" | "budget" | "google-level" | "anthropic-adaptive" | "anthropic-budget-effort"',
69
+ );
71
70
 
72
71
  const EFFORT_ORDER = ["minimal", "low", "medium", "high", "xhigh"] as const;
73
72
 
@@ -77,137 +76,141 @@ const EFFORT_ORDER = ["minimal", "low", "medium", "high", "xhigh"] as const;
77
76
  * `ThinkingConfig` (ordered `efforts`, never empty). Precedence mirrors the
78
77
  * old runtime: explicit `levels` beat the min..max range; `efforts` beats both.
79
78
  */
80
- const ModelThinkingSchema = z
81
- .object({
82
- mode: ThinkingControlModeSchema,
83
- efforts: z.array(EffortSchema).min(1).optional(),
84
- defaultLevel: EffortSchema.optional(),
85
- effortMap: ReasoningEffortMapSchema.optional(),
86
- supportsDisplay: z.boolean().optional(),
87
- // Legacy range vocabulary (pre-efforts configs).
88
- minLevel: EffortSchema.optional(),
89
- maxLevel: EffortSchema.optional(),
90
- levels: z.array(EffortSchema).min(1).optional(),
91
- })
92
- .refine(
93
- value =>
79
+ const ModelThinkingSchema = type({
80
+ mode: ThinkingControlModeSchema,
81
+ "efforts?": EffortSchema.array(),
82
+ "defaultLevel?": EffortSchema,
83
+ "effortMap?": ReasoningEffortMapSchema,
84
+ "supportsDisplay?": "boolean",
85
+ // Legacy range vocabulary (pre-efforts configs).
86
+ "minLevel?": EffortSchema,
87
+ "maxLevel?": EffortSchema,
88
+ "levels?": EffortSchema.array(),
89
+ })
90
+ .narrow(
91
+ (value, ctx) =>
94
92
  value.efforts !== undefined ||
95
93
  value.levels !== undefined ||
96
- (value.minLevel !== undefined && value.maxLevel !== undefined),
97
- {
98
- message: "thinking requires `efforts` (or legacy `levels`/`minLevel`+`maxLevel`)",
99
- },
94
+ (value.minLevel !== undefined && value.maxLevel !== undefined) ||
95
+ ctx.mustBe("thinking with `efforts` (or legacy `levels`/`minLevel`+`maxLevel`)"),
100
96
  )
101
- .transform(({ efforts, levels, minLevel, maxLevel, mode, defaultLevel, effortMap, supportsDisplay }) => {
102
- let resolved = efforts ?? levels;
97
+ .pipe((value: any) => {
98
+ let resolved = value.efforts ?? value.levels;
103
99
  if (!resolved) {
104
- const minIndex = EFFORT_ORDER.indexOf(minLevel!);
105
- const maxIndex = EFFORT_ORDER.indexOf(maxLevel!);
100
+ const minIndex = EFFORT_ORDER.indexOf(value.minLevel!);
101
+ const maxIndex = EFFORT_ORDER.indexOf(value.maxLevel!);
106
102
  resolved = EFFORT_ORDER.slice(minIndex, Math.max(minIndex, maxIndex) + 1);
107
103
  }
108
104
  return {
109
- mode,
105
+ mode: value.mode,
110
106
  efforts: resolved,
111
- ...(defaultLevel !== undefined && { defaultLevel }),
112
- ...(effortMap !== undefined && { effortMap }),
113
- ...(supportsDisplay !== undefined && { supportsDisplay }),
107
+ ...(value.defaultLevel !== undefined && { defaultLevel: value.defaultLevel }),
108
+ ...(value.effortMap !== undefined && { effortMap: value.effortMap }),
109
+ ...(value.supportsDisplay !== undefined && { supportsDisplay: value.supportsDisplay }),
114
110
  };
115
111
  });
116
112
 
117
- const ModelDefinitionSchema = z.object({
118
- id: z.string().min(1),
119
- name: z.string().min(1).optional(),
120
- api: z
121
- .enum([
122
- "openai-completions",
123
- "openai-responses",
124
- "openai-codex-responses",
125
- "azure-openai-responses",
126
- "anthropic-messages",
127
- "google-generative-ai",
128
- "google-gemini-cli",
129
- "google-vertex",
130
- ])
131
- .optional(),
132
- baseUrl: z.string().min(1).optional(),
133
- reasoning: z.boolean().optional(),
134
- thinking: ModelThinkingSchema.optional(),
135
- input: z.array(z.enum(["text", "image"])).optional(),
136
- supportsTools: z.boolean().optional(),
137
- cost: z
138
- .object({
139
- input: z.number(),
140
- output: z.number(),
141
- cacheRead: z.number(),
142
- cacheWrite: z.number(),
143
- })
144
- .optional(),
145
- premiumMultiplier: z.number().optional(),
146
- contextWindow: z.number().optional(),
147
- maxTokens: z.number().optional(),
148
- omitMaxOutputTokens: z.boolean().optional(),
149
- headers: z.record(z.string(), z.string()).optional(),
150
- compat: OpenAICompatSchema.optional(),
151
- contextPromotionTarget: z.string().min(1).optional(),
113
+ const ModelDefinitionSchema = type({
114
+ id: "string",
115
+ "name?": "string",
116
+ "api?":
117
+ '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
118
+ "baseUrl?": "string",
119
+ "reasoning?": "boolean",
120
+ "thinking?": ModelThinkingSchema,
121
+ "input?": '("text" | "image")[]',
122
+ "supportsTools?": "boolean",
123
+ "cost?": {
124
+ input: "number",
125
+ output: "number",
126
+ cacheRead: "number",
127
+ cacheWrite: "number",
128
+ },
129
+ "premiumMultiplier?": "number",
130
+ "contextWindow?": "number",
131
+ "maxTokens?": "number",
132
+ "omitMaxOutputTokens?": "boolean",
133
+ "headers?": { "[string]": "string" },
134
+ "compat?": OpenAICompatSchema,
135
+ "contextPromotionTarget?": "string",
136
+ }).narrow((value, ctx) => {
137
+ // Enforce id non-empty
138
+ if (typeof value.id === "string" && value.id.length === 0) {
139
+ return ctx.mustBe("id a non-empty string");
140
+ }
141
+ if (value.name !== undefined && typeof value.name === "string" && value.name.length === 0) {
142
+ return ctx.mustBe("name a non-empty string");
143
+ }
144
+ if (value.baseUrl !== undefined && typeof value.baseUrl === "string" && value.baseUrl.length === 0) {
145
+ return ctx.mustBe("baseUrl a non-empty string");
146
+ }
147
+ if (
148
+ value.contextPromotionTarget !== undefined &&
149
+ typeof value.contextPromotionTarget === "string" &&
150
+ value.contextPromotionTarget.length === 0
151
+ ) {
152
+ return ctx.mustBe("contextPromotionTarget a non-empty string");
153
+ }
154
+ return true;
152
155
  });
153
156
 
154
- export const ModelOverrideSchema = z.object({
155
- name: z.string().min(1).optional(),
156
- reasoning: z.boolean().optional(),
157
- thinking: ModelThinkingSchema.optional(),
158
- input: z.array(z.enum(["text", "image"])).optional(),
159
- supportsTools: z.boolean().optional(),
160
- cost: z
161
- .object({
162
- input: z.number().optional(),
163
- output: z.number().optional(),
164
- cacheRead: z.number().optional(),
165
- cacheWrite: z.number().optional(),
166
- })
167
- .optional(),
168
- premiumMultiplier: z.number().optional(),
169
- contextWindow: z.number().optional(),
170
- maxTokens: z.number().optional(),
171
- omitMaxOutputTokens: z.boolean().optional(),
172
- headers: z.record(z.string(), z.string()).optional(),
173
- compat: OpenAICompatSchema.optional(),
174
- contextPromotionTarget: z.string().min(1).optional(),
157
+ export const ModelOverrideSchema = type({
158
+ "name?": "string",
159
+ "reasoning?": "boolean",
160
+ "thinking?": ModelThinkingSchema,
161
+ "input?": '("text" | "image")[]',
162
+ "supportsTools?": "boolean",
163
+ "cost?": {
164
+ "input?": "number",
165
+ "output?": "number",
166
+ "cacheRead?": "number",
167
+ "cacheWrite?": "number",
168
+ },
169
+ "premiumMultiplier?": "number",
170
+ "contextWindow?": "number",
171
+ "maxTokens?": "number",
172
+ "omitMaxOutputTokens?": "boolean",
173
+ "headers?": { "[string]": "string" },
174
+ "compat?": OpenAICompatSchema,
175
+ "contextPromotionTarget?": "string",
176
+ }).narrow((value, ctx) => {
177
+ if (value.name !== undefined && typeof value.name === "string" && value.name.length === 0) {
178
+ return ctx.mustBe("name a non-empty string");
179
+ }
180
+ if (
181
+ value.contextPromotionTarget !== undefined &&
182
+ typeof value.contextPromotionTarget === "string" &&
183
+ value.contextPromotionTarget.length === 0
184
+ ) {
185
+ return ctx.mustBe("contextPromotionTarget a non-empty string");
186
+ }
187
+ return true;
175
188
  });
176
189
 
177
- export type ModelOverride = z.infer<typeof ModelOverrideSchema>;
190
+ export type ModelOverride = typeof ModelOverrideSchema.infer;
178
191
 
179
- export const ProviderDiscoverySchema = z.object({
180
- type: z.enum(["ollama", "llama.cpp", "lm-studio", "openai-models-list", "proxy"]),
192
+ export const ProviderDiscoverySchema = type({
193
+ type: '"ollama" | "llama.cpp" | "lm-studio" | "openai-models-list" | "proxy"',
181
194
  });
182
195
 
183
- export const ProviderAuthSchema = z.enum(["apiKey", "none", "oauth"]);
184
-
185
- export type ProviderAuthMode = z.infer<typeof ProviderAuthSchema>;
186
- export type ProviderDiscovery = z.infer<typeof ProviderDiscoverySchema>;
187
-
188
- const ProviderConfigSchema = z.object({
189
- baseUrl: z.string().min(1).optional(),
190
- apiKey: z.string().min(1).optional(),
191
- api: z
192
- .enum([
193
- "openai-completions",
194
- "openai-responses",
195
- "openai-codex-responses",
196
- "azure-openai-responses",
197
- "anthropic-messages",
198
- "google-generative-ai",
199
- "google-gemini-cli",
200
- "google-vertex",
201
- ])
202
- .optional(),
203
- headers: z.record(z.string(), z.string()).optional(),
204
- compat: OpenAICompatSchema.optional(),
205
- authHeader: z.boolean().optional(),
206
- auth: ProviderAuthSchema.optional(),
207
- discovery: ProviderDiscoverySchema.optional(),
208
- models: z.array(ModelDefinitionSchema).optional(),
209
- modelOverrides: z.record(z.string(), ModelOverrideSchema).optional(),
210
- disableStrictTools: z.boolean().optional(),
196
+ export const ProviderAuthSchema = type('"apiKey" | "none" | "oauth"');
197
+
198
+ export type ProviderAuthMode = typeof ProviderAuthSchema.infer;
199
+ export type ProviderDiscovery = typeof ProviderDiscoverySchema.infer;
200
+
201
+ const ProviderConfigSchema = type({
202
+ "baseUrl?": "string",
203
+ "apiKey?": "string",
204
+ "api?":
205
+ '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
206
+ "headers?": { "[string]": "string" },
207
+ "compat?": OpenAICompatSchema,
208
+ "authHeader?": "boolean",
209
+ "auth?": ProviderAuthSchema,
210
+ "discovery?": ProviderDiscoverySchema,
211
+ "models?": ModelDefinitionSchema.array(),
212
+ "modelOverrides?": { "[string]": ModelOverrideSchema },
213
+ "disableStrictTools?": "boolean",
211
214
  /**
212
215
  * Streaming transport override. When set to `"pi-native"`, omp dispatches
213
216
  * every model under this provider via the auth-gateway's
@@ -215,17 +218,41 @@ const ProviderConfigSchema = z.object({
215
218
  * provider's `baseUrl` must point at a compatible `omp auth-gateway`
216
219
  * and `apiKey` must carry the gateway bearer.
217
220
  */
218
- transport: z.literal("pi-native").optional(),
221
+ "transport?": '"pi-native"',
222
+ }).narrow((value, ctx) => {
223
+ if (value.baseUrl !== undefined && typeof value.baseUrl === "string" && value.baseUrl.length === 0) {
224
+ return ctx.mustBe("baseUrl a non-empty string");
225
+ }
226
+ if (value.apiKey !== undefined && typeof value.apiKey === "string" && value.apiKey.length === 0) {
227
+ return ctx.mustBe("apiKey a non-empty string");
228
+ }
229
+ return true;
219
230
  });
220
231
 
221
- const EquivalenceConfigSchema = z.object({
222
- overrides: z.record(z.string(), z.string().min(1)).optional(),
223
- exclude: z.array(z.string().min(1)).optional(),
232
+ const EquivalenceConfigSchema = type({
233
+ "overrides?": { "[string]": "string" },
234
+ "exclude?": "string[]",
235
+ }).narrow((value, ctx) => {
236
+ if (value.overrides !== undefined) {
237
+ for (const [, v] of Object.entries(value.overrides)) {
238
+ if (typeof v === "string" && v.length === 0) {
239
+ return ctx.mustBe("overrides values non-empty strings");
240
+ }
241
+ }
242
+ }
243
+ if (value.exclude !== undefined && Array.isArray(value.exclude)) {
244
+ for (const item of value.exclude) {
245
+ if (typeof item === "string" && item.length === 0) {
246
+ return ctx.mustBe("exclude items non-empty strings");
247
+ }
248
+ }
249
+ }
250
+ return true;
224
251
  });
225
252
 
226
- export const ModelsConfigSchema = z.object({
227
- providers: z.record(z.string(), ProviderConfigSchema).optional(),
228
- equivalence: EquivalenceConfigSchema.optional(),
253
+ export const ModelsConfigSchema = type({
254
+ "providers?": { "[string]": ProviderConfigSchema },
255
+ "equivalence?": EquivalenceConfigSchema,
229
256
  });
230
257
 
231
- export type ModelsConfig = z.infer<typeof ModelsConfigSchema>;
258
+ export type ModelsConfig = typeof ModelsConfigSchema.infer;
@@ -897,6 +897,28 @@ export const SETTINGS_SCHEMA = {
897
897
  },
898
898
  },
899
899
 
900
+ "model.loopGuard.enabled": {
901
+ type: "boolean",
902
+ default: true,
903
+ ui: {
904
+ tab: "model",
905
+ group: "Thinking",
906
+ label: "Loop Guard",
907
+ description: "Enable automatic stream loop detection for Gemini and DeepSeek models",
908
+ },
909
+ },
910
+
911
+ "model.loopGuard.checkAssistantContent": {
912
+ type: "boolean",
913
+ default: true,
914
+ ui: {
915
+ tab: "model",
916
+ group: "Thinking",
917
+ label: "Loop Guard Scan Prose",
918
+ description: "Apply loop guard to assistant prose messages in addition to thinking logs",
919
+ },
920
+ },
921
+
900
922
  repeatToolDescriptions: {
901
923
  type: "boolean",
902
924
  default: false,
@@ -1336,6 +1358,18 @@ export const SETTINGS_SCHEMA = {
1336
1358
  },
1337
1359
  },
1338
1360
 
1361
+ "startup.showSplash": {
1362
+ type: "boolean",
1363
+ default: false,
1364
+ ui: {
1365
+ tab: "interaction",
1366
+ group: "Startup & Updates",
1367
+ label: "Show Startup Splash",
1368
+ description:
1369
+ "Show the full animated setup splash on normal interactive startup without rerunning setup. Quiet Startup still suppresses it.",
1370
+ },
1371
+ },
1372
+
1339
1373
  "startup.setupWizard": {
1340
1374
  type: "boolean",
1341
1375
  default: true,
@@ -19,6 +19,7 @@ import tsImportType from "./ts-import-type.md" with { type: "text" };
19
19
  import tsNoAny from "./ts-no-any.md" with { type: "text" };
20
20
  import tsNoDeprecatedLeftovers from "./ts-no-deprecated-leftovers.md" with { type: "text" };
21
21
  import tsNoDynamicImport from "./ts-no-dynamic-import.md" with { type: "text" };
22
+ import tsNoInlineCastAccess from "./ts-no-inline-cast-access.md" with { type: "text" };
22
23
  import tsNoReturnType from "./ts-no-return-type.md" with { type: "text" };
23
24
  import tsNoTestTimers from "./ts-no-test-timers.md" with { type: "text" };
24
25
  import tsNoTinyFunctions from "./ts-no-tiny-functions.md" with { type: "text" };
@@ -45,6 +46,7 @@ export const BUILTIN_RULE_SOURCES: readonly BuiltinRuleSource[] = [
45
46
  { name: "ts-no-any", content: tsNoAny },
46
47
  { name: "ts-no-deprecated-leftovers", content: tsNoDeprecatedLeftovers },
47
48
  { name: "ts-no-dynamic-import", content: tsNoDynamicImport },
49
+ { name: "ts-no-inline-cast-access", content: tsNoInlineCastAccess },
48
50
  { name: "ts-no-return-type", content: tsNoReturnType },
49
51
  { name: "ts-no-test-timers", content: tsNoTestTimers },
50
52
  { name: "ts-no-tiny-functions", content: tsNoTinyFunctions },
@@ -17,7 +17,7 @@ Use top-level `import type` declarations for type-only dependencies. NEVER write
17
17
 
18
18
  ```typescript
19
19
  // Bad — inline imports hide dependencies in signatures.
20
- function run(client: import("some-sdk").Client, input: import("zod/v4").infer<Schema>): Promise<Output>;
20
+ function run(client: import("some-sdk").Client, input: import("arktype").infer<Schema>): Promise<Output>;
21
21
 
22
22
  // Bad — annotations become path dumps.
23
23
  const options: import("some-sdk/config").ClientOptions = { ... };
@@ -28,7 +28,7 @@ const options: import("some-sdk/config").ClientOptions = { ... };
28
28
  ```typescript
29
29
  import type { Client } from "some-sdk";
30
30
  import type { ClientOptions } from "some-sdk/config";
31
- import type { infer as Infer } from "zod/v4";
31
+ import type { infer as Infer } from "arktype";
32
32
 
33
33
  function run(client: Client, input: Infer<Schema>): Promise<Output>;
34
34
  const options: ClientOptions = { ... };
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Never use `any` in TypeScript annotations or assertions — use `unknown`, generics, or the actual type"
2
+ description: "Never use `any` in TypeScript annotations or assertions — use `unknown`, generics, a schema parse at trust boundaries, or the actual type"
3
3
  condition: ": any|as any"
4
4
  scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
5
  ---
@@ -9,6 +9,7 @@ Never use `: any` or `as any`. They disable type checking exactly where the boun
9
9
  ## Use instead
10
10
 
11
11
  - `unknown` for unvalidated input.
12
+ - A schema parse (Zod, Valibot, …; e.g. Zod v4 when it is already in the project) for untrusted or external input — validate once, then consume a typed value.
12
13
  - A domain type when the shape is known.
13
14
  - A generic when the caller supplies the shape.
14
15
  - A type guard when runtime checks establish shape.
@@ -25,7 +26,7 @@ function readId(value: any): any {
25
26
  // Good — validate unknown input.
26
27
  function readId(value: unknown): string | undefined {
27
28
  if (value && typeof value === "object" && "id" in value) {
28
- const candidate = (value as { id: unknown }).id;
29
+ const candidate = value.id; // `in` narrowing types this as unknown — no cast needed
29
30
  return typeof candidate === "string" ? candidate : undefined;
30
31
  }
31
32
  }
@@ -53,4 +54,12 @@ const config = { port: 3000 } as any as ServerConfig;
53
54
  const config = { port: 3000 } satisfies ServerConfig;
54
55
  ```
55
56
 
57
+ ## Choosing: guard vs schema vs unchecked cast
58
+
59
+ | Situation | Reach for |
60
+ | --- | --- |
61
+ | Data from outside your control — network/RPC, parsed JSON, config files, env vars, CLI/IPC, persisted blobs — or a shape reused across the codebase | **Schema parse** (Zod/Valibot/…): runtime validation, typed output, and a clear error on bad shape |
62
+ | In-process value the compiler merely lost track of — an `unknown` from a generic, a union to discriminate, a one-off read of a field or two | **Type guard** (`in` / `typeof`): no dependency, but it only checks what you write, so keep the checked surface small |
63
+ | You genuinely know more than the compiler *and* a runtime check is impossible or meaningless — a well-known DOM node (`as HTMLElement`), structurally-identical types inference can't unify, a library type that is wrong or unexpressible, `as const` | **Unchecked cast** (`as` / `as unknown as T`): assign to a named const with a one-line reason; never for raw external input |
64
+
56
65
  If a library boundary truly requires an unchecked cast, use `as unknown as T` with a short reason. Never leave a bare `any`.
@@ -0,0 +1,55 @@
1
+ ---
2
+ description: "Don't assert an inline object type and immediately read a property — `(x as { y: T }).y` trusts an unchecked shape; validate with a schema parse at trust boundaries, narrow with `in`/`typeof`, or use a validated named type"
3
+ scope: "tool:edit(*.{ts,tsx,mts,cts}), tool:write(*.{ts,tsx,mts,cts})"
4
+ interruptMode: tool-only
5
+ astCondition:
6
+ - "($X as { $$$BODY }).$PROP"
7
+ - "($X as { $$$BODY })?.$PROP"
8
+ - "($X as { $$$BODY })[$IDX]"
9
+ ---
10
+
11
+ **Don't assert an inline object type just to read a property.** `(value as { content: unknown }).content` fabricates a shape the compiler never verified, then trusts it for exactly one access. If `value` isn't that shape, the read is silently wrong and no type error ever fires.
12
+
13
+ ## Why it's wrong
14
+
15
+ - The cast is an unchecked assertion — it suppresses the type error instead of proving the shape.
16
+ - It localizes the lie to one expression, so the next reader can't tell whether the value was ever validated.
17
+ - It almost always stands in for the real fix: runtime narrowing or a validated type at the boundary.
18
+
19
+ ## Avoid
20
+
21
+ ```ts
22
+ const content = (value as { content: unknown }).content;
23
+ const id = (resp as { data: { id: string } }).data.id;
24
+ const name = (payload as { name?: string })?.name;
25
+ const flag = (opts as { enabled: boolean })["enabled"];
26
+ ```
27
+
28
+ ## Use
29
+
30
+ Prefer a schema parse at the boundary when a validator is available (Zod, Valibot, …) — validate once, then read from a fully typed value. If Zod is already in the project (e.g. Zod v4):
31
+
32
+ ```ts
33
+ import { z } from "zod/v4";
34
+
35
+ const Resp = z.object({ data: z.object({ id: z.string() }) });
36
+
37
+ const resp = Resp.parse(raw); // throws on bad input; resp.data.id is typed string
38
+ const id = resp.data.id;
39
+ ```
40
+
41
+ For a one-off read of a single field, narrow with `in` / `typeof` so the access is actually checked — TypeScript infers `unknown` for the property after `"content" in value`:
42
+
43
+ ```ts
44
+ if (value && typeof value === "object" && "content" in value) {
45
+ const content = value.content; // unknown — validate before use
46
+ }
47
+ ```
48
+
49
+ ## Choosing: guard vs schema vs unchecked cast
50
+
51
+ | Situation | Reach for |
52
+ | --- | --- |
53
+ | Data from outside your control — network/RPC, parsed JSON, config files, env vars, CLI/IPC, persisted blobs — or a shape reused across the codebase | **Schema parse** (Zod/Valibot/…): runtime validation, typed output, and a clear error on bad shape |
54
+ | In-process value the compiler merely lost track of — an `unknown` from a generic, a union to discriminate, a one-off read of a field or two | **Type guard** (`in` / `typeof`): no dependency, but it only checks what you write, so keep the checked surface small |
55
+ | You genuinely know more than the compiler *and* a runtime check is impossible or meaningless — a well-known DOM node (`as HTMLElement`), structurally-identical types inference can't unify, a library type that's wrong or unexpressible, `as const` | **Unchecked cast** (`as`): assign to a named const with a one-line reason; never for raw external input, never inlined into a member access |