pi-t3-bridge 0.6.0 → 0.7.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 (95) hide show
  1. package/package.json +3 -4
  2. package/src/amplike/btw.ts +5 -7
  3. package/src/amplike/handoff.ts +33 -23
  4. package/src/amplike/package.ts +37 -24
  5. package/src/amplike/selection.ts +26 -14
  6. package/src/bridge/accounts.ts +66 -49
  7. package/src/bridge/ask.ts +73 -93
  8. package/src/bridge/bash-timeout.ts +15 -8
  9. package/src/bridge/config.ts +29 -26
  10. package/src/bridge/plan.ts +19 -11
  11. package/src/bridge/resume.ts +17 -11
  12. package/src/bridge/state.ts +54 -34
  13. package/src/bridge/steer-judge.ts +130 -96
  14. package/src/bridge/steer-transcript.ts +4 -3
  15. package/src/bridge/steer.ts +16 -10
  16. package/src/bridge/t3-json.ts +14 -19
  17. package/src/bridge/tasks.ts +39 -31
  18. package/src/bridge/thread.ts +5 -6
  19. package/src/bridge/transcript.ts +11 -8
  20. package/src/pstack/brief.ts +4 -3
  21. package/src/pstack/child.ts +45 -32
  22. package/src/pstack/models.ts +138 -106
  23. package/src/pstack/overlay.ts +18 -20
  24. package/src/pstack/results.ts +4 -7
  25. package/src/pstack/sandbox.ts +12 -8
  26. package/pstack/skills/statechart/scripts/node_modules/.package-lock.json +0 -46
  27. package/pstack/skills/statechart/scripts/node_modules/fast-check/LICENSE +0 -21
  28. package/pstack/skills/statechart/scripts/node_modules/fast-check/README.md +0 -263
  29. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/cjs/fast-check.d.ts +0 -5165
  30. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/cjs/fast-check.js +0 -12147
  31. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/cjs/package.json +0 -3
  32. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/cjs/types57/fast-check.d.ts +0 -5165
  33. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/fast-check.d.ts +0 -5165
  34. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/fast-check.js +0 -12011
  35. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/rolldown-runtime-D7D4PA-g.js +0 -13
  36. package/pstack/skills/statechart/scripts/node_modules/fast-check/lib/types57/fast-check.d.ts +0 -5165
  37. package/pstack/skills/statechart/scripts/node_modules/fast-check/package.json +0 -94
  38. package/pstack/skills/statechart/scripts/node_modules/pure-rand/LICENSE +0 -21
  39. package/pstack/skills/statechart/scripts/node_modules/pure-rand/README.md +0 -168
  40. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/RandomGenerator-DcXj09Ch.d.ts +0 -14
  41. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformBigInt.d.ts +0 -15
  42. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformBigInt.js +0 -38
  43. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformFloat32.d.ts +0 -15
  44. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformFloat32.js +0 -18
  45. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformFloat64.d.ts +0 -15
  46. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformFloat64.js +0 -22
  47. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformInt.d.ts +0 -15
  48. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/distribution/uniformInt.js +0 -134
  49. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/RandomGenerator-DcXj09Ch.d.ts +0 -14
  50. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.d.ts +0 -15
  51. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.js +0 -37
  52. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.d.ts +0 -15
  53. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.js +0 -17
  54. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.d.ts +0 -15
  55. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.js +0 -21
  56. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformInt.d.ts +0 -15
  57. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/distribution/uniformInt.js +0 -133
  58. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/congruential32.d.ts +0 -7
  59. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/congruential32.js +0 -44
  60. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/mersenne.d.ts +0 -7
  61. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/mersenne.js +0 -90
  62. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.d.ts +0 -7
  63. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.js +0 -80
  64. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/xorshift128plus.d.ts +0 -7
  65. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/generator/xorshift128plus.js +0 -78
  66. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/package.json +0 -3
  67. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.d.ts +0 -16
  68. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.js +0 -0
  69. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/types/RandomGenerator.d.ts +0 -2
  70. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/types/RandomGenerator.js +0 -0
  71. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/generateN.d.ts +0 -6
  72. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/generateN.js +0 -8
  73. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/purify.d.ts +0 -12
  74. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/purify.js +0 -9
  75. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/skipN.d.ts +0 -6
  76. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/esm/utils/skipN.js +0 -6
  77. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/congruential32.d.ts +0 -7
  78. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/congruential32.js +0 -46
  79. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/mersenne.d.ts +0 -7
  80. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/mersenne.js +0 -92
  81. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/xoroshiro128plus.d.ts +0 -7
  82. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/xoroshiro128plus.js +0 -82
  83. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/xorshift128plus.d.ts +0 -7
  84. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/generator/xorshift128plus.js +0 -80
  85. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/types/JumpableRandomGenerator.d.ts +0 -16
  86. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/types/JumpableRandomGenerator.js +0 -0
  87. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/types/RandomGenerator.d.ts +0 -2
  88. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/types/RandomGenerator.js +0 -0
  89. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/generateN.d.ts +0 -6
  90. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/generateN.js +0 -9
  91. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/purify.d.ts +0 -12
  92. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/purify.js +0 -10
  93. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/skipN.d.ts +0 -6
  94. package/pstack/skills/statechart/scripts/node_modules/pure-rand/lib/utils/skipN.js +0 -7
  95. package/pstack/skills/statechart/scripts/node_modules/pure-rand/package.json +0 -133
package/src/bridge/ask.ts CHANGED
@@ -4,103 +4,79 @@ import type {
4
4
  ExtensionUIContext,
5
5
  ExtensionUIDialogOptions,
6
6
  } from "@earendil-works/pi-coding-agent";
7
- import { Type } from "typebox";
8
- import type { Static } from "typebox";
9
- import { Value } from "typebox/value";
7
+ import {
8
+ arr,
9
+ compile,
10
+ int,
11
+ lit,
12
+ obj,
13
+ oneOf,
14
+ opt,
15
+ str,
16
+ union,
17
+ } from "@earendil-works/pi-schema";
18
+ import type { Static } from "@earendil-works/pi-schema";
10
19
 
11
20
  import { registerUnlessTaken } from "./claim.ts";
12
21
  import { insideT3 } from "./t3-http.ts";
13
22
 
14
23
  const OWN_ANSWER = "Type my own answer";
15
24
 
16
- const ChoiceSchema = Type.Object(
17
- {
18
- description: Type.Optional(
19
- Type.String({
20
- description: "Why to pick it, shown under the question.",
21
- minLength: 1,
22
- })
23
- ),
24
- label: Type.String({ description: "The choice.", minLength: 1 }),
25
- },
26
- { additionalProperties: false }
27
- );
28
-
29
- const QuestionSchema = Type.Object(
30
- {
31
- body: Type.Optional(
32
- Type.String({
33
- description: "More context, shown under the title.",
34
- minLength: 1,
35
- })
36
- ),
37
- choices: Type.Optional(
38
- Type.Array(ChoiceSchema, {
39
- description: "Choices to pick from. Leave out for free text.",
40
- minItems: 1,
41
- })
42
- ),
43
- title: Type.String({ description: "The question.", minLength: 1 }),
44
- },
45
- { additionalProperties: false }
46
- );
47
-
48
- const AskInputSchema = Type.Object(
49
- {
50
- questions: Type.Array(QuestionSchema, {
51
- description: "Questions to ask in order, one card each.",
52
- minItems: 1,
53
- }),
54
- timeoutSeconds: Type.Optional(
55
- Type.Integer({
56
- description: "Seconds to wait for all answers before giving up.",
57
- minimum: 10,
58
- })
59
- ),
60
- },
61
- { additionalProperties: false }
62
- );
25
+ const ChoiceSchema = obj({
26
+ description: opt(
27
+ str({
28
+ description: "Why to pick it, shown under the question.",
29
+ minLength: 1,
30
+ })
31
+ ),
32
+ label: str({ description: "The choice.", minLength: 1 }),
33
+ });
63
34
 
64
- const AnswerSchema = Type.Union([
65
- Type.Object(
66
- {
67
- index: Type.Integer(),
68
- kind: Type.Literal("chose"),
69
- label: Type.String(),
70
- question: Type.Integer(),
71
- },
72
- { additionalProperties: false }
35
+ const QuestionSchema = obj({
36
+ body: opt(
37
+ str({
38
+ description: "More context, shown under the title.",
39
+ minLength: 1,
40
+ })
73
41
  ),
74
- Type.Object(
75
- {
76
- kind: Type.Literal("typed"),
77
- question: Type.Integer(),
78
- text: Type.String(),
79
- },
80
- { additionalProperties: false }
42
+ choices: opt(
43
+ arr(ChoiceSchema, {
44
+ description: "Choices to pick from. Leave out for free text.",
45
+ minItems: 1,
46
+ })
81
47
  ),
82
- Type.Object(
83
- { kind: Type.Literal("rephrase"), question: Type.Integer() },
84
- { additionalProperties: false }
48
+ title: str({ description: "The question.", minLength: 1 }),
49
+ });
50
+
51
+ const AskInputSchema = obj({
52
+ questions: arr(QuestionSchema, {
53
+ description: "Questions to ask in order, one card each.",
54
+ minItems: 1,
55
+ }),
56
+ timeoutSeconds: opt(
57
+ int({
58
+ description: "Seconds to wait for all answers before giving up.",
59
+ minimum: 10,
60
+ })
85
61
  ),
62
+ });
63
+
64
+ const AnswerSchema = union([
65
+ obj({ index: int(), kind: lit("chose"), label: str(), question: int() }),
66
+ obj({ kind: lit("typed"), question: int(), text: str() }),
67
+ obj({ kind: lit("rephrase"), question: int() }),
86
68
  ]);
87
69
 
88
- const StopSchema = Type.Object(
89
- {
90
- kind: Type.Union([Type.Literal("dismissed"), Type.Literal("timed_out")]),
91
- question: Type.Integer(),
92
- unasked: Type.Integer(),
93
- },
94
- { additionalProperties: false }
95
- );
70
+ const StopSchema = obj({
71
+ kind: oneOf(["dismissed", "timed_out"]),
72
+ question: int(),
73
+ unasked: int(),
74
+ });
96
75
 
97
- const AskDetailsSchema = Type.Object(
98
- {
99
- answers: Type.Array(AnswerSchema),
100
- stop: Type.Optional(StopSchema),
101
- },
102
- { additionalProperties: false }
103
- );
76
+ const AskDetailsSchema = obj({
77
+ answers: arr(AnswerSchema),
78
+ stop: opt(StopSchema),
79
+ });
104
80
 
105
81
  type Question = Static<typeof QuestionSchema>;
106
82
 
@@ -115,15 +91,19 @@ type AskDetails = Static<typeof AskDetailsSchema>;
115
91
  type Surface = "t3" | "terminal";
116
92
 
117
93
  // The call as the model sent it, before AskInputSchema rejects extra fields.
118
- const RawChoiceSchema = Type.Object({});
94
+ const RawChoiceSchema = obj({}, { open: true });
119
95
 
120
- const RawQuestionSchema = Type.Object({
121
- choices: Type.Optional(Type.Array(RawChoiceSchema)),
122
- });
96
+ const RawQuestionSchema = obj(
97
+ { choices: opt(arr(RawChoiceSchema)) },
98
+ { open: true }
99
+ );
123
100
 
124
- const RawAskSchema = Type.Object({
125
- questions: Type.Optional(Type.Array(RawQuestionSchema)),
126
- });
101
+ const RawAskSchema = obj(
102
+ { questions: opt(arr(RawQuestionSchema)) },
103
+ { open: true }
104
+ );
105
+
106
+ const isRawAsk = compile(RawAskSchema).check;
127
107
 
128
108
  type RawCall =
129
109
  | Static<typeof RawAskSchema>
@@ -294,7 +274,7 @@ const askTool = defineTool({
294
274
  annotations: { readOnlyHint: true },
295
275
  description:
296
276
  'Ask the user and wait for the answer. Each question becomes one card, asked in order. Put your recommendation first and mark it in the label, e.g. "SQLite (recommended)". Put the reason in `description`, which is shown under the question. The user can always type their own answer instead of picking. To let them pick several, say so in `body`. They will type something like "1 and 3" or "all". Leave out `choices` for free text. If a typed answer is a question or pushback, reply to it before you act. If the user dismisses a card, stop and wait for their next message.',
297
- execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
277
+ execute: async (_toolCallId, params: AskInput, signal, _onUpdate, ctx) => {
298
278
  if (!ctx.hasUI) {
299
279
  throw new Error(
300
280
  "ask needs an interactive client, such as T3 Code or the Pi terminal."
@@ -373,7 +353,7 @@ const askTool = defineTool({
373
353
  outputSchema: AskDetailsSchema,
374
354
  parameters: AskInputSchema,
375
355
  prepareArguments: (args) => {
376
- if (Value.Check(RawAskSchema, args)) {
356
+ if (isRawAsk(args)) {
377
357
  checkFields(args);
378
358
  }
379
359
 
@@ -1,6 +1,12 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { Type } from "typebox";
3
- import { Value } from "typebox/value";
2
+ import {
3
+ compile,
4
+ num,
5
+ obj,
6
+ opt,
7
+ str,
8
+ unknown,
9
+ } from "@earendil-works/pi-schema";
4
10
 
5
11
  import { readConfig } from "./config.ts";
6
12
 
@@ -13,10 +19,11 @@ const DEFAULT_SECONDS = 300;
13
19
  export const bashTimeoutSeconds = (): number =>
14
20
  readConfig().bashTimeoutSeconds ?? DEFAULT_SECONDS;
15
21
 
16
- const BashInputSchema = Type.Object({
17
- command: Type.String(),
18
- timeout: Type.Optional(Type.Unknown()),
19
- });
22
+ const isBashInput = compile(
23
+ obj({ command: str(), timeout: opt(unknown()) }, { open: true })
24
+ ).check;
25
+
26
+ const isNumber = compile(num()).check;
20
27
 
21
28
  const TIMED_OUT = /Command timed out after \d+ seconds/u;
22
29
 
@@ -24,14 +31,14 @@ export const registerBashTimeout = (pi: ExtensionAPI): void => {
24
31
  pi.on("tool_call", (event) => {
25
32
  const { input } = event;
26
33
 
27
- if (event.toolName !== "bash" || !Value.Check(BashInputSchema, input)) {
34
+ if (event.toolName !== "bash" || !isBashInput(input)) {
28
35
  return;
29
36
  }
30
37
 
31
38
  const cap = bashTimeoutSeconds();
32
39
 
33
40
  input.timeout =
34
- Value.Check(Type.Number(), input.timeout) && input.timeout > 0
41
+ isNumber(input.timeout) && input.timeout > 0
35
42
  ? Math.min(input.timeout, cap)
36
43
  : cap;
37
44
  });
@@ -3,24 +3,33 @@ import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
- import { Type } from "typebox";
7
- import type { Static, TSchema } from "typebox";
8
- import { Value } from "typebox/value";
6
+ import {
7
+ compile,
8
+ map,
9
+ num,
10
+ oneOf,
11
+ str,
12
+ unknown,
13
+ } from "@earendil-works/pi-schema";
14
+ import type { Static, Validator } from "@earendil-works/pi-schema";
9
15
 
10
16
  // The bridge's settings, one JSON file per machine. Each key is checked on its
11
17
  // own, so one bad value falls back to its default and leaves the rest alone.
12
18
  // The file is read on every call, so an edit applies on the next prompt.
13
19
 
14
- const SteerSchema = Type.Union([
15
- Type.Literal("on"),
16
- Type.Literal("off"),
17
- Type.Literal("shadow"),
18
- ]);
20
+ const SteerSchema = oneOf(["on", "off", "shadow"]);
19
21
 
20
- const ChildSandboxSchema = Type.Union([
21
- Type.Literal("on"),
22
- Type.Literal("off"),
23
- ]);
22
+ const ChildSandboxSchema = oneOf(["on", "off"]);
23
+
24
+ const configFile = compile(map(unknown()));
25
+
26
+ const fields = {
27
+ bashTimeoutSeconds: compile(num({ exclusiveMinimum: 0 })),
28
+ childSandbox: compile(ChildSandboxSchema),
29
+ classifierModel: compile(str({ pattern: "^[^/\\s]+/.+$" })),
30
+ mcpUrl: compile(str({ minLength: 1 })),
31
+ steer: compile(SteerSchema),
32
+ };
24
33
 
25
34
  export interface Config {
26
35
  readonly bashTimeoutSeconds: number | undefined;
@@ -60,19 +69,16 @@ const parse = (text: string): Loaded => {
60
69
  return { config: EMPTY, problems: [`${file} is not valid JSON.`] };
61
70
  }
62
71
 
63
- if (!Value.Check(Type.Record(Type.String(), Type.Unknown()), parsed)) {
72
+ if (!configFile.check(parsed)) {
64
73
  return { config: EMPTY, problems: [`${file} is not a JSON object.`] };
65
74
  }
66
75
 
67
76
  const problems: string[] = [];
68
77
 
69
- const field = <S extends TSchema>(
70
- key: string,
71
- schema: S
72
- ): Static<S> | undefined => {
78
+ const field = <T>(key: string, validator: Validator<T>): T | undefined => {
73
79
  const value = parsed[key];
74
80
 
75
- if (value === undefined || Value.Check(schema, value)) {
81
+ if (value === undefined || validator.check(value)) {
76
82
  return value;
77
83
  }
78
84
 
@@ -87,15 +93,12 @@ const parse = (text: string): Loaded => {
87
93
  config: {
88
94
  bashTimeoutSeconds: field(
89
95
  "bashTimeoutSeconds",
90
- Type.Number({ exclusiveMinimum: 0 })
91
- ),
92
- childSandbox: field("childSandbox", ChildSandboxSchema),
93
- classifierModel: field(
94
- "classifierModel",
95
- Type.String({ pattern: "^[^/\\s]+/.+$" })
96
+ fields.bashTimeoutSeconds
96
97
  ),
97
- mcpUrl: field("mcpUrl", Type.String({ minLength: 1 })),
98
- steer: field("steer", SteerSchema),
98
+ childSandbox: field("childSandbox", fields.childSandbox),
99
+ classifierModel: field("classifierModel", fields.classifierModel),
100
+ mcpUrl: field("mcpUrl", fields.mcpUrl),
101
+ steer: field("steer", fields.steer),
99
102
  },
100
103
  problems,
101
104
  };
@@ -3,7 +3,8 @@ import type {
3
3
  ExtensionAPI,
4
4
  ExtensionContext,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import { Type } from "typebox";
6
+ import { obj, opt, str } from "@earendil-works/pi-schema";
7
+ import type { Static } from "@earendil-works/pi-schema";
7
8
 
8
9
  import {
9
10
  escapeHtml,
@@ -117,15 +118,16 @@ const planCard = (title: string, plan: string): PlanCard => {
117
118
  };
118
119
  };
119
120
 
120
- const PlanSchema = Type.Object({
121
- plan: Type.String({
122
- description: "The plan in Markdown, as numbered steps.",
123
- minLength: 1,
124
- }),
125
- title: Type.Optional(
126
- Type.String({ description: "Short plan title.", minLength: 1 })
127
- ),
128
- });
121
+ const PlanSchema = obj(
122
+ {
123
+ plan: str({
124
+ description: "The plan in Markdown, as numbered steps.",
125
+ minLength: 1,
126
+ }),
127
+ title: opt(str({ description: "Short plan title.", minLength: 1 })),
128
+ },
129
+ { open: true }
130
+ );
129
131
 
130
132
  interface PlanToolDetails {
131
133
  readonly choice: PlanChoice | "dismissed";
@@ -177,7 +179,13 @@ export const registerPlanGate = (pi: ExtensionAPI): void => {
177
179
  annotations: { readOnlyHint: true },
178
180
  description:
179
181
  "Ask the user to approve a plan before any file changes. Until the user approves, only read-only tools run. On Revise, change the plan and call this tool again. On Cancel, stop: only read-only tools run until the user's next message.",
180
- execute: async (toolCallId, params, _signal, _onUpdate, ctx) => {
182
+ execute: async (
183
+ toolCallId,
184
+ params: Static<typeof PlanSchema>,
185
+ _signal,
186
+ _onUpdate,
187
+ ctx
188
+ ) => {
181
189
  if (!ctx.hasUI) {
182
190
  throw new Error(
183
191
  "Plan approval needs an interactive client, such as T3 Code or the Pi terminal."
@@ -2,9 +2,8 @@ import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
- import { Type } from "typebox";
6
- import type { Static } from "typebox";
7
- import { Value } from "typebox/value";
5
+ import { compile, obj, oneOf, opt, str } from "@earendil-works/pi-schema";
6
+ import type { Static } from "@earendil-works/pi-schema";
8
7
 
9
8
  import type { Classified } from "./failures.ts";
10
9
  import { callT3Tool, insideT3 } from "./t3-http.ts";
@@ -24,13 +23,20 @@ const AFTER_RESET_MS = 2 * 60_000;
24
23
 
25
24
  const MARKER = /\[pi-t3-resume (?<marker>[\w-]+)\]/u;
26
25
 
27
- const ResumeSchema = Type.Object({
28
- marker: Type.String(),
29
- scheduledTaskId: Type.Optional(Type.String()),
30
- state: Type.Union([Type.Literal("scheduled"), Type.Literal("done")]),
31
- });
26
+ const ResumeSchema = obj(
27
+ {
28
+ marker: str(),
29
+ scheduledTaskId: opt(str()),
30
+ state: oneOf(["scheduled", "done"]),
31
+ },
32
+ { open: true }
33
+ );
32
34
 
33
- const ScheduledSchema = Type.Object({ scheduledTaskId: Type.String() });
35
+ const isResume = compile(ResumeSchema).check;
36
+
37
+ const isScheduled = compile(
38
+ obj({ scheduledTaskId: str() }, { open: true })
39
+ ).check;
34
40
 
35
41
  const resumes = (ctx: ExtensionContext) =>
36
42
  ctx.sessionManager
@@ -38,7 +44,7 @@ const resumes = (ctx: ExtensionContext) =>
38
44
  .flatMap((entry) =>
39
45
  entry.type === "custom" &&
40
46
  entry.customType === RESUME_ENTRY &&
41
- Value.Check(ResumeSchema, entry.data)
47
+ isResume(entry.data)
42
48
  ? [entry.data]
43
49
  : []
44
50
  );
@@ -94,7 +100,7 @@ export const scheduleResume = async (
94
100
  state: "scheduled",
95
101
  };
96
102
 
97
- if (Value.Check(ScheduledSchema, result)) {
103
+ if (isScheduled(result)) {
98
104
  scheduled.scheduledTaskId = result.scheduledTaskId;
99
105
  }
100
106
 
@@ -9,9 +9,19 @@ import {
9
9
  import { homedir } from "node:os";
10
10
  import path from "node:path";
11
11
 
12
- import { Type } from "typebox";
13
- import type { Static } from "typebox";
14
- import { Value } from "typebox/value";
12
+ import {
13
+ compile,
14
+ lit,
15
+ map,
16
+ nullable,
17
+ num,
18
+ obj,
19
+ oneOf,
20
+ opt,
21
+ str,
22
+ union,
23
+ } from "@earendil-works/pi-schema";
24
+ import type { Static } from "@earendil-works/pi-schema";
15
25
 
16
26
  // Machine-wide bridge state, shared by every Pi session: which model families
17
27
  // are out of usage, and one ledger line per delegated brief.
@@ -38,24 +48,29 @@ export const moveOldState = (): void => {
38
48
  }
39
49
  };
40
50
 
41
- const FamilyRecordSchema = Type.Union([
42
- Type.Object({
43
- reason: Type.Union([Type.Literal("exhausted"), Type.Literal("unhealthy")]),
44
- resetAt: Type.Optional(Type.String()),
45
- since: Type.String(),
46
- source: Type.Union([Type.Literal("delegate"), Type.Literal("user")]),
47
- state: Type.Literal("exhausted"),
48
- }),
49
- Type.Object({
50
- childThreadId: Type.String(),
51
- since: Type.String(),
52
- state: Type.Literal("probing"),
53
- }),
51
+ const FamilyRecordSchema = union([
52
+ obj(
53
+ {
54
+ reason: oneOf(["exhausted", "unhealthy"]),
55
+ resetAt: opt(str()),
56
+ since: str(),
57
+ source: oneOf(["delegate", "user"]),
58
+ state: lit("exhausted"),
59
+ },
60
+ { open: true }
61
+ ),
62
+ obj(
63
+ { childThreadId: str(), since: str(), state: lit("probing") },
64
+ { open: true }
65
+ ),
54
66
  ]);
55
67
 
56
- const AccountsFileSchema = Type.Object({
57
- families: Type.Record(Type.String(), FamilyRecordSchema),
58
- });
68
+ const AccountsFileSchema = obj(
69
+ { families: map(FamilyRecordSchema) },
70
+ { open: true }
71
+ );
72
+
73
+ const isAccountsFile = compile(AccountsFileSchema).check;
59
74
 
60
75
  export type FamilyRecord = Static<typeof FamilyRecordSchema>;
61
76
 
@@ -67,7 +82,7 @@ export const readFamilies = (): AccountsFile["families"] => {
67
82
  try {
68
83
  const parsed: unknown = JSON.parse(readFileSync(accountsPath(), "utf-8"));
69
84
 
70
- return Value.Check(AccountsFileSchema, parsed) ? parsed.families : {};
85
+ return isAccountsFile(parsed) ? parsed.families : {};
71
86
  } catch {
72
87
  return {};
73
88
  }
@@ -132,19 +147,24 @@ export const appendSteerLog = (line: {
132
147
  }
133
148
  };
134
149
 
135
- const LedgerLineSchema = Type.Object({
136
- cacheRead: Type.Optional(Type.Number()),
137
- cacheWrite: Type.Optional(Type.Number()),
138
- cost: Type.Optional(Type.Number()),
139
- input: Type.Optional(Type.Number()),
140
- job: Type.Optional(Type.Union([Type.String(), Type.Null()])),
141
- minutes: Type.Optional(Type.Number()),
142
- model: Type.Optional(Type.Union([Type.String(), Type.Null()])),
143
- report: Type.Optional(Type.String()),
144
- status: Type.Optional(Type.String()),
145
- threadId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
146
- tokens: Type.Optional(Type.Number()),
147
- });
150
+ const LedgerLineSchema = obj(
151
+ {
152
+ cacheRead: opt(num()),
153
+ cacheWrite: opt(num()),
154
+ cost: opt(num()),
155
+ input: opt(num()),
156
+ job: opt(nullable(str())),
157
+ minutes: opt(num()),
158
+ model: opt(nullable(str())),
159
+ report: opt(str()),
160
+ status: opt(str()),
161
+ threadId: opt(nullable(str())),
162
+ tokens: opt(num()),
163
+ },
164
+ { open: true }
165
+ );
166
+
167
+ const isLedgerLine = compile(LedgerLineSchema).check;
148
168
 
149
169
  export type LedgerLine = Static<typeof LedgerLineSchema>;
150
170
 
@@ -156,7 +176,7 @@ export const readLedger = (): LedgerLine[] => {
156
176
  try {
157
177
  const parsed: unknown = JSON.parse(line);
158
178
 
159
- return Value.Check(LedgerLineSchema, parsed) ? [parsed] : [];
179
+ return isLedgerLine(parsed) ? [parsed] : [];
160
180
  } catch {
161
181
  return [];
162
182
  }