@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/examples/custom-tools/README.md +11 -7
  3. package/examples/custom-tools/hello/index.ts +2 -2
  4. package/examples/extensions/README.md +19 -8
  5. package/examples/extensions/api-demo.ts +15 -19
  6. package/examples/extensions/hello.ts +5 -6
  7. package/examples/extensions/plan-mode.ts +1 -1
  8. package/examples/extensions/reload-runtime.ts +4 -3
  9. package/examples/extensions/with-deps/index.ts +4 -3
  10. package/examples/sdk/06-extensions.ts +4 -2
  11. package/package.json +8 -18
  12. package/src/autoresearch/tools/init-experiment.ts +38 -41
  13. package/src/autoresearch/tools/log-experiment.ts +32 -41
  14. package/src/autoresearch/tools/run-experiment.ts +3 -3
  15. package/src/autoresearch/tools/update-notes.ts +11 -11
  16. package/src/commands/commit.ts +10 -0
  17. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  18. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  19. package/src/commit/agentic/tools/git-hunk.ts +5 -5
  20. package/src/commit/agentic/tools/git-overview.ts +4 -4
  21. package/src/commit/agentic/tools/propose-changelog.ts +13 -13
  22. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  23. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  24. package/src/commit/agentic/tools/schemas.ts +28 -28
  25. package/src/commit/agentic/tools/split-commit.ts +22 -21
  26. package/src/commit/analysis/summary.ts +4 -4
  27. package/src/commit/changelog/generate.ts +7 -11
  28. package/src/commit/shared-llm.ts +22 -34
  29. package/src/config/config-file.ts +35 -13
  30. package/src/config/model-registry.ts +40 -191
  31. package/src/config/models-config-schema.ts +166 -0
  32. package/src/config/settings-schema.ts +29 -0
  33. package/src/discovery/claude-plugins.ts +19 -7
  34. package/src/edit/index.ts +2 -2
  35. package/src/edit/modes/apply-patch.ts +7 -6
  36. package/src/edit/modes/patch.ts +18 -25
  37. package/src/edit/modes/replace.ts +18 -20
  38. package/src/eval/js/shared/rewrite-imports.ts +131 -10
  39. package/src/eval/py/executor.ts +233 -623
  40. package/src/eval/py/kernel.ts +27 -2
  41. package/src/eval/py/runner.py +42 -11
  42. package/src/eval/py/runtime.ts +1 -0
  43. package/src/exa/factory.ts +5 -4
  44. package/src/exa/mcp-client.ts +1 -1
  45. package/src/exa/researcher.ts +9 -20
  46. package/src/exa/search.ts +26 -52
  47. package/src/exa/types.ts +1 -1
  48. package/src/exa/websets.ts +54 -53
  49. package/src/exec/bash-executor.ts +2 -1
  50. package/src/extensibility/custom-commands/loader.ts +5 -3
  51. package/src/extensibility/custom-commands/types.ts +4 -2
  52. package/src/extensibility/custom-tools/loader.ts +5 -3
  53. package/src/extensibility/custom-tools/types.ts +7 -6
  54. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  55. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  56. package/src/extensibility/extensions/loader.ts +7 -3
  57. package/src/extensibility/extensions/types.ts +9 -5
  58. package/src/extensibility/extensions/wrapper.ts +1 -2
  59. package/src/extensibility/hooks/loader.ts +3 -1
  60. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  61. package/src/extensibility/hooks/types.ts +4 -2
  62. package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
  63. package/src/extensibility/shared-events.ts +1 -1
  64. package/src/extensibility/typebox.ts +391 -0
  65. package/src/goals/tools/goal-tool.ts +6 -12
  66. package/src/hashline/input.ts +2 -1
  67. package/src/hashline/parser.ts +27 -3
  68. package/src/hashline/types.ts +4 -4
  69. package/src/hindsight/state.ts +2 -2
  70. package/src/index.ts +0 -2
  71. package/src/internal-urls/docs-index.generated.ts +15 -15
  72. package/src/internal-urls/router.ts +8 -0
  73. package/src/internal-urls/types.ts +21 -0
  74. package/src/lsp/config.ts +15 -6
  75. package/src/lsp/defaults.json +6 -2
  76. package/src/lsp/types.ts +30 -38
  77. package/src/mcp/manager.ts +1 -1
  78. package/src/mcp/tool-bridge.ts +1 -1
  79. package/src/modes/acp/acp-agent.ts +248 -50
  80. package/src/modes/components/session-observer-overlay.ts +12 -1
  81. package/src/modes/components/status-line/segments.ts +39 -4
  82. package/src/modes/controllers/command-controller.ts +27 -2
  83. package/src/modes/controllers/event-controller.ts +3 -4
  84. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  85. package/src/modes/interactive-mode.ts +1 -1
  86. package/src/modes/rpc/host-tools.ts +1 -1
  87. package/src/modes/rpc/host-uris.ts +235 -0
  88. package/src/modes/rpc/rpc-client.ts +1 -1
  89. package/src/modes/rpc/rpc-mode.ts +27 -1
  90. package/src/modes/rpc/rpc-types.ts +58 -1
  91. package/src/modes/runtime-init.ts +2 -1
  92. package/src/modes/theme/defaults/dark-poimandres.json +1 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +1 -0
  94. package/src/modes/theme/theme.ts +117 -117
  95. package/src/modes/types.ts +1 -1
  96. package/src/modes/utils/context-usage.ts +2 -2
  97. package/src/prompts/tools/github.md +4 -4
  98. package/src/prompts/tools/hashline.md +22 -26
  99. package/src/prompts/tools/read.md +55 -37
  100. package/src/sdk.ts +31 -8
  101. package/src/session/agent-session.ts +74 -104
  102. package/src/session/messages.ts +16 -51
  103. package/src/session/session-manager.ts +22 -2
  104. package/src/session/streaming-output.ts +16 -6
  105. package/src/task/discovery.ts +5 -2
  106. package/src/task/executor.ts +210 -87
  107. package/src/task/index.ts +15 -11
  108. package/src/task/render.ts +32 -5
  109. package/src/task/types.ts +54 -39
  110. package/src/tools/ask.ts +12 -12
  111. package/src/tools/ast-edit.ts +11 -15
  112. package/src/tools/ast-grep.ts +9 -10
  113. package/src/tools/bash-command-fixup.ts +47 -0
  114. package/src/tools/bash.ts +48 -38
  115. package/src/tools/browser/render.ts +2 -2
  116. package/src/tools/browser.ts +39 -53
  117. package/src/tools/calculator.ts +12 -11
  118. package/src/tools/checkpoint.ts +7 -7
  119. package/src/tools/debug.ts +40 -43
  120. package/src/tools/eval.ts +16 -10
  121. package/src/tools/find.ts +10 -13
  122. package/src/tools/gh.ts +108 -132
  123. package/src/tools/hindsight-recall.ts +4 -6
  124. package/src/tools/hindsight-reflect.ts +5 -5
  125. package/src/tools/hindsight-retain.ts +15 -17
  126. package/src/tools/image-gen.ts +31 -81
  127. package/src/tools/index.ts +4 -1
  128. package/src/tools/inspect-image.ts +8 -9
  129. package/src/tools/irc.ts +15 -27
  130. package/src/tools/job.ts +30 -28
  131. package/src/tools/output-meta.ts +26 -0
  132. package/src/tools/read.ts +39 -12
  133. package/src/tools/recipe/index.ts +7 -9
  134. package/src/tools/render-mermaid.ts +12 -12
  135. package/src/tools/report-tool-issue.ts +4 -4
  136. package/src/tools/resolve.ts +11 -11
  137. package/src/tools/review.ts +14 -26
  138. package/src/tools/search-tool-bm25.ts +7 -9
  139. package/src/tools/search.ts +19 -22
  140. package/src/tools/ssh.ts +10 -9
  141. package/src/tools/todo-write.ts +26 -34
  142. package/src/tools/vim.ts +10 -26
  143. package/src/tools/write.ts +25 -5
  144. package/src/tools/yield.ts +100 -54
  145. package/src/web/search/index.ts +9 -24
  146. package/src/web/search/providers/anthropic.ts +5 -0
  147. package/src/web/search/providers/exa.ts +3 -0
  148. package/src/web/search/providers/gemini.ts +5 -0
  149. package/src/web/search/providers/jina.ts +5 -2
  150. package/src/web/search/providers/zai.ts +5 -2
  151. package/src/prompts/compaction/branch-summary-context.md +0 -5
  152. package/src/prompts/compaction/branch-summary-preamble.md +0 -2
  153. package/src/prompts/compaction/branch-summary.md +0 -30
  154. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  155. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  156. package/src/prompts/compaction/compaction-summary.md +0 -38
  157. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  158. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  159. package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
  160. package/src/prompts/system/file-operations.md +0 -10
  161. package/src/prompts/system/handoff-document.md +0 -49
  162. package/src/prompts/system/summarization-system.md +0 -3
  163. package/src/session/compaction/branch-summarization.ts +0 -324
  164. package/src/session/compaction/compaction.ts +0 -1420
  165. package/src/session/compaction/errors.ts +0 -31
  166. package/src/session/compaction/index.ts +0 -8
  167. package/src/session/compaction/pruning.ts +0 -91
  168. package/src/session/compaction/utils.ts +0 -184
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Minimal `@sinclair/typebox` runtime compatibility shim, backed by Zod.
3
+ *
4
+ * Historically the coding agent injected the real `@sinclair/typebox` (~5MB
5
+ * dependency) into extensions, hooks, custom tools, and custom commands so
6
+ * they could author parameter schemas as `Type.Object({ name: Type.String() })`.
7
+ * Internally everything already runs through Zod (`wire.ts`, `validation.ts`);
8
+ * the only reason TypeBox remained was extension-author compat.
9
+ *
10
+ * This module replaces that injection with a tiny façade whose `Type` builders
11
+ * return Zod schemas. Output is indistinguishable from hand-written Zod inside
12
+ * the agent pipeline:
13
+ *
14
+ * - `isZodSchema()` keys off the Zod `_zod` marker that every schema carries.
15
+ * - `zodToWireSchema()` emits the same draft-7 JSON Schema providers expect
16
+ * from TypeBox-authored tools (defaulted fields treated as optional, etc.).
17
+ *
18
+ * The surface intentionally covers only the common TypeBox builders. Plugins
19
+ * that reached for niche TypeBox-only APIs (`TypeCompiler`, the global
20
+ * `TypeRegistry`, custom `Symbol(TypeBox.Kind)` introspection) must vendor
21
+ * `@sinclair/typebox` directly in their own package.
22
+ */
23
+
24
+ import { areJsonValuesEqual } from "@oh-my-pi/pi-ai/utils/schema";
25
+ import {
26
+ type ZodArray,
27
+ type ZodEnum,
28
+ type ZodObject,
29
+ type ZodOptional,
30
+ type ZodRawShape,
31
+ type ZodType,
32
+ z,
33
+ } from "zod/v4";
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Type aliases — exported so `import type { Static, TSchema } from "..."`
37
+ // patterns keep compiling at the call site.
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export type TSchema = ZodType;
41
+ export type Static<T extends ZodType> = z.infer<T>;
42
+ export type TAny = ZodType;
43
+ export type TUnknown = ZodType;
44
+ export type TNever = ZodType;
45
+ export type TNull = ZodType;
46
+ export type TString = z.ZodString;
47
+ export type TNumber = z.ZodNumber;
48
+ export type TInteger = z.ZodNumber;
49
+ export type TBoolean = z.ZodBoolean;
50
+ export type TLiteral<V extends string | number | boolean> = z.ZodLiteral<V>;
51
+ export type TArray<E extends ZodType> = ZodArray<E>;
52
+ export type TObject<P extends ZodRawShape = ZodRawShape> = ZodObject<P>;
53
+ export type TOptional<E extends ZodType> = ZodOptional<E>;
54
+ export type TUnion<_T extends readonly ZodType[] = readonly ZodType[]> = ZodType;
55
+ export type TEnum<T extends readonly (string | number)[] = readonly (string | number)[]> = ZodEnum<{
56
+ [K in T[number] as `${K}`]: K;
57
+ }>;
58
+ export type TRecord<_K extends ZodType, _V extends ZodType> = ZodType;
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Option shapes — loose subset of JSON Schema metadata + per-type constraints.
62
+ // ---------------------------------------------------------------------------
63
+
64
+ interface Meta {
65
+ title?: string;
66
+ description?: string;
67
+ default?: unknown;
68
+ examples?: unknown[];
69
+ // Real TypeBox accepts arbitrary extra JSON Schema keywords; we tolerate
70
+ // them silently so callers don't blow up on niche metadata.
71
+ [key: string]: unknown;
72
+ }
73
+
74
+ interface StringOpts extends Meta {
75
+ minLength?: number;
76
+ maxLength?: number;
77
+ pattern?: string;
78
+ format?: string;
79
+ }
80
+
81
+ interface NumberOpts extends Meta {
82
+ minimum?: number;
83
+ maximum?: number;
84
+ exclusiveMinimum?: number;
85
+ exclusiveMaximum?: number;
86
+ multipleOf?: number;
87
+ }
88
+
89
+ interface ArrayOpts extends Meta {
90
+ minItems?: number;
91
+ maxItems?: number;
92
+ uniqueItems?: boolean;
93
+ }
94
+
95
+ interface ObjectOpts extends Meta {
96
+ /**
97
+ * TypeBox default: extra keys are preserved. Set `false` to reject unknowns,
98
+ * `true` to allow any, or a schema to validate them.
99
+ */
100
+ additionalProperties?: boolean | ZodType;
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Helpers
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function withMeta<T extends ZodType>(schema: T, opts: Meta | undefined): T {
108
+ if (!opts) return schema;
109
+ let out: ZodType = schema;
110
+ if (typeof opts.description === "string") out = out.describe(opts.description);
111
+ if ("default" in opts) out = out.default(opts.default as never) as unknown as ZodType;
112
+
113
+ const metadata: Record<string, unknown> = {};
114
+ for (const [key, value] of Object.entries(opts)) {
115
+ if (key === "description" || key === "default" || key === "additionalProperties") continue;
116
+ metadata[key] = value;
117
+ }
118
+ if (Object.keys(metadata).length > 0) out = out.meta(metadata);
119
+ return out as T;
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Builders
124
+ // ---------------------------------------------------------------------------
125
+
126
+ function tString(opts?: StringOpts): ZodType {
127
+ let s: ZodType = z.string();
128
+ if (opts) {
129
+ // Format selection swaps the base schema for a more specific Zod string
130
+ // validator that emits the right `format` keyword in JSON Schema.
131
+ switch (opts.format) {
132
+ case "email":
133
+ s = z.email();
134
+ break;
135
+ case "url":
136
+ case "uri":
137
+ s = z.url();
138
+ break;
139
+ case "uuid":
140
+ s = z.uuid();
141
+ break;
142
+ case "date-time":
143
+ s = z.iso.datetime();
144
+ break;
145
+ case "date":
146
+ s = z.iso.date();
147
+ break;
148
+ case "time":
149
+ s = z.iso.time();
150
+ break;
151
+ case "ipv4":
152
+ s = z.ipv4();
153
+ break;
154
+ case "ipv6":
155
+ s = z.ipv6();
156
+ break;
157
+ default:
158
+ break;
159
+ }
160
+ // Length/pattern constraints live on the `_ZodString` base that every
161
+ // format-specific schema (ZodEmail, ZodURL, ZodISODateTime, ...) extends,
162
+ // so we apply them regardless of which concrete subclass `s` ended up as.
163
+ const sf = s as z.ZodString;
164
+ if (typeof opts.minLength === "number") s = sf.min(opts.minLength);
165
+ if (typeof opts.maxLength === "number") s = (s as z.ZodString).max(opts.maxLength);
166
+ if (typeof opts.pattern === "string") s = (s as z.ZodString).regex(new RegExp(opts.pattern));
167
+ }
168
+ return withMeta(s, opts);
169
+ }
170
+
171
+ function applyNumberConstraints(base: z.ZodNumber, opts: NumberOpts | undefined): z.ZodNumber {
172
+ if (!opts) return base;
173
+ let out = base;
174
+ if (typeof opts.minimum === "number") out = out.min(opts.minimum);
175
+ if (typeof opts.maximum === "number") out = out.max(opts.maximum);
176
+ if (typeof opts.exclusiveMinimum === "number") out = out.gt(opts.exclusiveMinimum);
177
+ if (typeof opts.exclusiveMaximum === "number") out = out.lt(opts.exclusiveMaximum);
178
+ if (typeof opts.multipleOf === "number") out = out.multipleOf(opts.multipleOf);
179
+ return out;
180
+ }
181
+
182
+ function tNumber(opts?: NumberOpts): ZodType {
183
+ return withMeta(applyNumberConstraints(z.number(), opts), opts);
184
+ }
185
+
186
+ function tInteger(opts?: NumberOpts): ZodType {
187
+ return withMeta(applyNumberConstraints(z.number().int(), opts), opts);
188
+ }
189
+
190
+ function tBoolean(opts?: Meta): ZodType {
191
+ return withMeta(z.boolean(), opts);
192
+ }
193
+
194
+ function tNull(opts?: Meta): ZodType {
195
+ return withMeta(z.null(), opts);
196
+ }
197
+
198
+ function tAny(opts?: Meta): ZodType {
199
+ return withMeta(z.any(), opts);
200
+ }
201
+
202
+ function tUnknown(opts?: Meta): ZodType {
203
+ return withMeta(z.unknown(), opts);
204
+ }
205
+
206
+ function tNever(opts?: Meta): ZodType {
207
+ return withMeta(z.never(), opts);
208
+ }
209
+
210
+ function tLiteral<V extends string | number | boolean>(value: V, opts?: Meta): ZodType {
211
+ return withMeta(z.literal(value), opts);
212
+ }
213
+
214
+ function tUnion<T extends readonly ZodType[]>(schemas: T, opts?: Meta): ZodType {
215
+ if (schemas.length === 0) return withMeta(z.never(), opts);
216
+ if (schemas.length === 1) return withMeta(schemas[0] as ZodType, opts);
217
+ return withMeta(z.union(schemas as unknown as [ZodType, ZodType, ...ZodType[]]), opts);
218
+ }
219
+
220
+ function tIntersect(schemas: readonly ZodType[], opts?: Meta): ZodType {
221
+ if (schemas.length === 0) return withMeta(z.unknown(), opts);
222
+ if (schemas.length === 1) return withMeta(schemas[0] as ZodType, opts);
223
+ let out: ZodType = schemas[0] as ZodType;
224
+ for (let i = 1; i < schemas.length; i++) out = z.intersection(out, schemas[i] as ZodType) as ZodType;
225
+ return withMeta(out, opts);
226
+ }
227
+
228
+ function isArrayIndexKey(key: string): boolean {
229
+ if (!/^(?:0|[1-9]\\d*)$/.test(key)) return false;
230
+ const index = Number(key);
231
+ return Number.isSafeInteger(index) && index >= 0;
232
+ }
233
+
234
+ function uniqueLiteralValues(values: readonly (string | number | boolean)[]): Array<string | number | boolean> {
235
+ const unique: Array<string | number | boolean> = [];
236
+ for (const value of values) {
237
+ if (!unique.some(existing => existing === value)) unique.push(value);
238
+ }
239
+ return unique;
240
+ }
241
+
242
+ function literalUnion(values: readonly (string | number | boolean)[], opts?: Meta): ZodType {
243
+ const unique = uniqueLiteralValues(values);
244
+ if (unique.length === 0) return withMeta(z.never(), opts);
245
+ if (unique.length === 1) return withMeta(z.literal(unique[0] as string | number | boolean), opts);
246
+ const schemas = unique.map(value => z.literal(value as string | number | boolean)) as unknown as [
247
+ ZodType,
248
+ ZodType,
249
+ ...ZodType[],
250
+ ];
251
+ return withMeta(z.union(schemas), opts);
252
+ }
253
+ function tEnum<T extends Record<string, string | number> | readonly (string | number)[]>(
254
+ values: T,
255
+ opts?: Meta,
256
+ ): ZodType {
257
+ const list = Array.isArray(values)
258
+ ? values
259
+ : Object.entries(values)
260
+ .filter(([key, value]) => !(isArrayIndexKey(key) && typeof value === "string"))
261
+ .map(([, value]) => value);
262
+ return literalUnion(list, opts);
263
+ }
264
+
265
+ function tArray<E extends ZodType>(item: E, opts?: ArrayOpts): ZodType {
266
+ let arr: ZodType = z.array(item);
267
+ if (opts) {
268
+ if (typeof opts.minItems === "number") arr = (arr as ZodArray<E>).min(opts.minItems);
269
+ if (typeof opts.maxItems === "number") arr = (arr as ZodArray<E>).max(opts.maxItems);
270
+ if (opts.uniqueItems === true) {
271
+ arr = arr.refine(items => {
272
+ if (!Array.isArray(items)) return true;
273
+ for (let i = 0; i < items.length; i += 1) {
274
+ for (let j = i + 1; j < items.length; j += 1) {
275
+ if (areJsonValuesEqual(items[i], items[j])) return false;
276
+ }
277
+ }
278
+ return true;
279
+ }, "Expected array items to be unique");
280
+ }
281
+ }
282
+ return withMeta(arr, opts);
283
+ }
284
+
285
+ function tTuple(items: readonly ZodType[], opts?: Meta): ZodType {
286
+ return withMeta(z.tuple(items as unknown as [ZodType, ...ZodType[]]) as unknown as ZodType, opts);
287
+ }
288
+
289
+ function isOptional(schema: ZodType): boolean {
290
+ const def = (schema as { _zod?: { def?: { type?: string } } })._zod?.def;
291
+ return def?.type === "optional";
292
+ }
293
+
294
+ function tObject<P extends ZodRawShape>(properties: P, opts?: ObjectOpts): ZodObject<P> {
295
+ // `z.object` automatically derives `required` from non-optional entries,
296
+ // so `Type.Optional(...)` flows through unchanged (Zod treats `.optional()`
297
+ // and `Type.Optional`-style wrappers identically).
298
+ let obj = z.object(properties);
299
+ const ap = opts?.additionalProperties;
300
+ if (ap === false) {
301
+ obj = obj.strict() as unknown as ZodObject<P>;
302
+ } else if (ap === undefined || ap === true) {
303
+ // TypeBox preserves unknown keys by default; Zod's default is `.strip()`.
304
+ obj = obj.loose() as unknown as ZodObject<P>;
305
+ } else {
306
+ obj = obj.catchall(ap) as unknown as ZodObject<P>;
307
+ }
308
+ return withMeta(obj, opts);
309
+ }
310
+
311
+ function tRecord<V extends ZodType>(key: ZodType, value: V, opts?: Meta): ZodType {
312
+ return withMeta(z.record(key as never, value as never) as unknown as ZodType, opts);
313
+ }
314
+
315
+ function tOptional<E extends ZodType>(schema: E, _opts?: Meta): ZodOptional<E> {
316
+ return isOptional(schema) ? (schema as unknown as ZodOptional<E>) : (schema.optional() as ZodOptional<E>);
317
+ }
318
+
319
+ function tNullable<E extends ZodType>(schema: E, opts?: Meta): ZodType {
320
+ return withMeta(schema.nullable() as ZodType, opts);
321
+ }
322
+
323
+ function tReadonly<E extends ZodType>(schema: E): E {
324
+ // TypeBox's `Type.Readonly` is purely a marker; runtime parsing is identical.
325
+ return schema;
326
+ }
327
+
328
+ function tPartial<P extends ZodRawShape>(obj: ZodObject<P>): ZodObject<P> {
329
+ return obj.partial() as unknown as ZodObject<P>;
330
+ }
331
+
332
+ function tRequired<P extends ZodRawShape>(obj: ZodObject<P>): ZodObject<P> {
333
+ return obj.required() as unknown as ZodObject<P>;
334
+ }
335
+
336
+ function tPick<P extends ZodRawShape, K extends keyof P>(obj: ZodObject<P>, keys: readonly K[]): ZodObject<Pick<P, K>> {
337
+ const mask = Object.fromEntries(keys.map(k => [k as string, true]));
338
+ return obj.pick(mask as never) as unknown as ZodObject<Pick<P, K>>;
339
+ }
340
+
341
+ function tOmit<P extends ZodRawShape, K extends keyof P>(obj: ZodObject<P>, keys: readonly K[]): ZodObject<Omit<P, K>> {
342
+ const mask = Object.fromEntries(keys.map(k => [k as string, true]));
343
+ return obj.omit(mask as never) as unknown as ZodObject<Omit<P, K>>;
344
+ }
345
+
346
+ function tComposite(objects: readonly ZodObject<ZodRawShape>[], opts?: Meta): ZodObject<ZodRawShape> {
347
+ // `Type.Composite([...])` flattens every object schema into one object schema
348
+ // rather than producing an intersection. Mirror that via repeated `extend`.
349
+ if (objects.length === 0) return withMeta(z.object({}), opts) as ZodObject<ZodRawShape>;
350
+ let out = objects[0] as ZodObject<ZodRawShape>;
351
+ for (let i = 1; i < objects.length; i += 1) {
352
+ out = out.extend(objects[i].shape) as ZodObject<ZodRawShape>;
353
+ }
354
+ return withMeta(out, opts) as ZodObject<ZodRawShape>;
355
+ }
356
+
357
+ // ---------------------------------------------------------------------------
358
+ // Public `Type` namespace
359
+ // ---------------------------------------------------------------------------
360
+
361
+ export const Type = {
362
+ String: tString,
363
+ Number: tNumber,
364
+ Integer: tInteger,
365
+ Boolean: tBoolean,
366
+ Null: tNull,
367
+ Any: tAny,
368
+ Unknown: tUnknown,
369
+ Never: tNever,
370
+ Literal: tLiteral,
371
+ Union: tUnion,
372
+ Intersect: tIntersect,
373
+ Enum: tEnum,
374
+ Array: tArray,
375
+ Tuple: tTuple,
376
+ Object: tObject,
377
+ Record: tRecord,
378
+ Optional: tOptional,
379
+ Nullable: tNullable,
380
+ Readonly: tReadonly,
381
+ Partial: tPartial,
382
+ Required: tRequired,
383
+ Pick: tPick,
384
+ Omit: tOmit,
385
+ Composite: tComposite,
386
+ } as const;
387
+
388
+ export type TypeBuilder = typeof Type;
389
+
390
+ /** Default namespace export so `import * as typebox from "./typebox"` still resolves the `Type` key. */
391
+ export default { Type };
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
2
2
  import type { Component } from "@oh-my-pi/pi-tui";
3
3
  import { Text } from "@oh-my-pi/pi-tui";
4
4
  import { formatNumber, prompt } from "@oh-my-pi/pi-utils";
5
- import { type Static, Type } from "@sinclair/typebox";
5
+ import * as z from "zod/v4";
6
6
  import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
7
7
  import type { Theme, ThemeColor } from "../../modes/theme/theme";
8
8
  import goalDescription from "../../prompts/tools/goal.md" with { type: "text" };
@@ -14,19 +14,13 @@ import { renderStatusLine, truncateToWidth } from "../../tui";
14
14
  import { completionBudgetReport, remainingTokens } from "../runtime";
15
15
  import type { Goal, GoalStatus, GoalToolDetails } from "../state";
16
16
 
17
- const goalSchema = Type.Object({
18
- op: Type.Union([Type.Literal("create"), Type.Literal("get"), Type.Literal("complete")], {
19
- description: "Goal operation.",
20
- }),
21
- objective: Type.Optional(Type.String({ description: "Goal objective. Required when op=create." })),
22
- token_budget: Type.Optional(
23
- Type.Integer({
24
- description: "Optional positive token budget. Only honored when op=create.",
25
- }),
26
- ),
17
+ const goalSchema = z.object({
18
+ op: z.union([z.literal("create"), z.literal("get"), z.literal("complete")]).describe("Goal operation."),
19
+ objective: z.string().describe("Goal objective. Required when op=create.").optional(),
20
+ token_budget: z.number().int().describe("Optional positive token budget. Only honored when op=create.").optional(),
27
21
  });
28
22
 
29
- export type GoalToolInput = Static<typeof goalSchema>;
23
+ export type GoalToolInput = z.infer<typeof goalSchema>;
30
24
 
31
25
  export interface GoalToolResponse {
32
26
  goal: Goal | null;
@@ -108,7 +108,8 @@ export function splitHashlineInputs(input: string, options: SplitHashlineOptions
108
108
 
109
109
  const flush = () => {
110
110
  if (currentPath.length === 0) return;
111
- sections.push({ path: currentPath, diff: currentLines.join("\n") });
111
+ const hasOps = currentLines.some(rawLine => stripTrailingCarriageReturn(rawLine).trim().length > 0);
112
+ if (hasOps) sections.push({ path: currentPath, diff: currentLines.join("\n") });
112
113
  currentLines = [];
113
114
  };
114
115
 
@@ -86,9 +86,33 @@ function collectPayload(
86
86
  let index = startIndex;
87
87
  while (index < lines.length) {
88
88
  const line = stripTrailingCarriageReturn(lines[index]);
89
- if (!line.startsWith(HL_EDIT_SEP)) break;
90
- payload.push(line.slice(1));
91
- index++;
89
+ if (line.startsWith(HL_EDIT_SEP)) {
90
+ payload.push(line.slice(1));
91
+ index++;
92
+ continue;
93
+ }
94
+ // Silently recover from a missing payload prefix on an otherwise blank
95
+ // line: if more payload follows (possibly past further blanks), treat
96
+ // each intervening blank as an empty `${HL_EDIT_SEP}` payload line.
97
+ // Additionally, when the op explicitly requires payload (`+`/`<`) and
98
+ // we have not collected any yet, accept the blank(s) themselves as the
99
+ // empty payload — common typo of forgetting the `${HL_EDIT_SEP}` prefix
100
+ // when inserting a blank line.
101
+ if (line.length === 0) {
102
+ let lookahead = index + 1;
103
+ while (lookahead < lines.length && stripTrailingCarriageReturn(lines[lookahead]).length === 0) {
104
+ lookahead++;
105
+ }
106
+ const followedByPayload =
107
+ lookahead < lines.length && stripTrailingCarriageReturn(lines[lookahead]).startsWith(HL_EDIT_SEP);
108
+ const acceptBareBlank = requirePayload && payload.length === 0;
109
+ if (followedByPayload || acceptBareBlank) {
110
+ for (let j = index; j < lookahead; j++) payload.push("");
111
+ index = lookahead;
112
+ continue;
113
+ }
114
+ }
115
+ break;
92
116
  }
93
117
  if (payload.length === 0 && requirePayload) {
94
118
  throw new Error(`line ${opLineNum}: + and < operations require at least one ${HL_EDIT_SEP}TEXT payload line.`);
@@ -1,5 +1,4 @@
1
- import type { Static } from "@sinclair/typebox";
2
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
3
2
  import type { LspBatchRequest } from "../edit/renderer";
4
3
  import type { WritethroughCallback, WritethroughDeferredHandle } from "../lsp";
5
4
  import type { ToolSession } from "../tools";
@@ -26,8 +25,9 @@ export type HashlineEdit =
26
25
  | { kind: "insert"; cursor: HashlineCursor; text: string; lineNum: number; index: number }
27
26
  | { kind: "delete"; anchor: Anchor; lineNum: number; index: number; oldAssertion?: string };
28
27
 
29
- export const hashlineEditParamsSchema = Type.Object({ input: Type.String() });
30
- export type HashlineParams = Static<typeof hashlineEditParamsSchema>;
28
+ /** `path` is accepted by the edit tool runtime; other extra keys are preserved. */
29
+ export const hashlineEditParamsSchema = z.object({ input: z.string(), path: z.string().optional() }).passthrough();
30
+ export type HashlineParams = z.infer<typeof hashlineEditParamsSchema>;
31
31
 
32
32
  export interface HashlineStreamOptions {
33
33
  /** First line number to use when formatting (1-indexed). */
@@ -346,7 +346,7 @@ export class HindsightSessionState {
346
346
  async maybeRecallOnAgentStart(): Promise<void> {
347
347
  if (!this.config.autoRecall || this.hasRecalledForFirstTurn) return;
348
348
  const messages = extractMessages(this.session.sessionManager);
349
- const lastUser = [...messages].reverse().find(m => m.role === "user");
349
+ const lastUser = messages.findLast(m => m.role === "user");
350
350
  if (!lastUser) return;
351
351
 
352
352
  const query = composeRecallQuery(lastUser.content, messages, this.config.recallContextTurns);
@@ -386,7 +386,7 @@ export class HindsightSessionState {
386
386
  }
387
387
 
388
388
  async recallForCompaction(messages: HindsightMessage[]): Promise<string | undefined> {
389
- const lastUser = [...messages].reverse().find(m => m.role === "user");
389
+ const lastUser = messages.findLast(m => m.role === "user");
390
390
  if (!lastUser) return undefined;
391
391
 
392
392
  const query = composeRecallQuery(lastUser.content, messages, this.config.recallContextTurns);
package/src/index.ts CHANGED
@@ -43,8 +43,6 @@ export * from "./sdk";
43
43
  export * from "./session/agent-session";
44
44
  // Auth and model registry
45
45
  export * from "./session/auth-storage";
46
- // Compaction
47
- export * from "./session/compaction";
48
46
  export * from "./session/messages";
49
47
  export * from "./session/session-dump-format";
50
48
  export * from "./session/session-manager";