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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-t3-bridge",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Pi extension adapter for t3-pstack inside T3 Code.",
5
5
  "keywords": [
6
6
  "pi-extension",
@@ -50,6 +50,7 @@
50
50
  "typecheck": "tsc --project tsconfig.json && tsc --project pstack/skills/poteto-mode/scripts/watch-pr/tsconfig.json && tsc --project pstack/skills/statechart/scripts/tsconfig.json"
51
51
  },
52
52
  "dependencies": {
53
+ "@earendil-works/pi-schema": "1.5.0",
53
54
  "commander": "^15.0.0",
54
55
  "marked": "18.0.11",
55
56
  "pele": "1.13.0"
@@ -61,14 +62,12 @@
61
62
  "oxfmt": "^0.74.0",
62
63
  "oxlint": "^1.89.0",
63
64
  "testcontainers": "^12.2.1",
64
- "typebox": "1.3.27",
65
65
  "typescript": "^7.0.2",
66
66
  "ultracite": "^8.0.1"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@earendil-works/pi-ai": "*",
70
- "@earendil-works/pi-coding-agent": "*",
71
- "typebox": "*"
70
+ "@earendil-works/pi-coding-agent": "*"
72
71
  },
73
72
  "peerDependenciesMeta": {
74
73
  "@earendil-works/pi-coding-agent": {
@@ -1,6 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { Type } from "typebox";
3
- import { Value } from "typebox/value";
2
+ import { compile, nullable, obj, opt, str } from "@earendil-works/pi-schema";
4
3
 
5
4
  import { callT3Tool } from "../bridge/t3-http.ts";
6
5
  import type { LoadModeSpec } from "./package.ts";
@@ -14,10 +13,9 @@ import type { SelectionChange } from "./selection.ts";
14
13
 
15
14
  type Command = Parameters<ExtensionAPI["registerCommand"]>[1];
16
15
 
17
- const DelegatedSchema = Type.Object({
18
- link: Type.Optional(Type.Union([Type.String(), Type.Null()])),
19
- taskId: Type.String(),
20
- });
16
+ const isDelegated = compile(
17
+ obj({ link: opt(nullable(str())), taskId: str() }, { open: true })
18
+ ).check;
21
19
 
22
20
  const TITLE_LENGTH = 80;
23
21
 
@@ -76,7 +74,7 @@ export const btwCommand = (loadModeSpec: LoadModeSpec): Command => ({
76
74
  target: await targetFor(change),
77
75
  });
78
76
 
79
- if (!Value.Check(DelegatedSchema, delegated)) {
77
+ if (!isDelegated(delegated)) {
80
78
  throw new Error("delegate_task returned no task.");
81
79
  }
82
80
 
@@ -8,8 +8,8 @@ import type {
8
8
  ExtensionAPI,
9
9
  ExtensionContext,
10
10
  } from "@earendil-works/pi-coding-agent";
11
- import { Type } from "typebox";
12
- import { Value } from "typebox/value";
11
+ import { compile, nullable, obj, opt, str } from "@earendil-works/pi-schema";
12
+ import type { Static } from "@earendil-works/pi-schema";
13
13
 
14
14
  import { callT3Tool } from "../bridge/t3-http.ts";
15
15
  import type { LoadModeSpec } from "./package.ts";
@@ -39,10 +39,28 @@ Output only the prompt, with no preamble.`;
39
39
 
40
40
  const TITLE_LENGTH = 80;
41
41
 
42
- const LaunchSchema = Type.Object({
43
- link: Type.Optional(Type.Union([Type.String(), Type.Null()])),
44
- threadId: Type.String(),
45
- });
42
+ const isLaunched = compile(
43
+ obj({ link: opt(nullable(str())), threadId: str() }, { open: true })
44
+ ).check;
45
+
46
+ const HandoffSchema = obj(
47
+ {
48
+ goal: str({ description: "The goal/task for the new thread" }),
49
+ mode: opt(
50
+ str({
51
+ description:
52
+ "Amplike mode name to start the new thread with (e.g. 'rush', 'smart', 'deep')",
53
+ })
54
+ ),
55
+ model: opt(
56
+ str({
57
+ description:
58
+ "Model to start the new thread with, as provider/modelId (e.g. 'anthropic/claude-haiku-4-5')",
59
+ })
60
+ ),
61
+ },
62
+ { open: true }
63
+ );
46
64
 
47
65
  const summarize = async (
48
66
  ctx: ExtensionContext,
@@ -123,7 +141,7 @@ const handoff = async (
123
141
  modelSelection: { ...applyChange(await currentSelection(), change) },
124
142
  });
125
143
 
126
- if (!Value.Check(LaunchSchema, launched)) {
144
+ if (!isLaunched(launched)) {
127
145
  throw new Error("t3_thread_launch returned no thread.");
128
146
  }
129
147
 
@@ -134,7 +152,13 @@ export const handoffTool = (loadModeSpec: LoadModeSpec) =>
134
152
  defineTool({
135
153
  description:
136
154
  "Transfer context to a new focused T3 thread. ONLY use this when the user explicitly asks for a handoff. Provide a goal describing what the new thread should focus on. The result is the new thread's link, which you show the user verbatim.",
137
- execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
155
+ execute: async (
156
+ _toolCallId,
157
+ params: Static<typeof HandoffSchema>,
158
+ signal,
159
+ _onUpdate,
160
+ ctx
161
+ ) => {
138
162
  const link = await handoff(
139
163
  ctx,
140
164
  params.goal,
@@ -150,21 +174,7 @@ export const handoffTool = (loadModeSpec: LoadModeSpec) =>
150
174
  },
151
175
  label: "Handoff",
152
176
  name: "handoff",
153
- parameters: Type.Object({
154
- goal: Type.String({ description: "The goal/task for the new thread" }),
155
- mode: Type.Optional(
156
- Type.String({
157
- description:
158
- "Amplike mode name to start the new thread with (e.g. 'rush', 'smart', 'deep')",
159
- })
160
- ),
161
- model: Type.Optional(
162
- Type.String({
163
- description:
164
- "Model to start the new thread with, as provider/modelId (e.g. 'anthropic/claude-haiku-4-5')",
165
- })
166
- ),
167
- }),
177
+ parameters: HandoffSchema,
168
178
  });
169
179
 
170
180
  export const handoffCommand = (loadModeSpec: LoadModeSpec): Command => ({
@@ -10,27 +10,38 @@ import type {
10
10
  ExtensionFactory,
11
11
  ResolvedResource,
12
12
  } from "@earendil-works/pi-coding-agent";
13
- import { Type } from "typebox";
14
- import type { Static } from "typebox";
15
- import { Value } from "typebox/value";
13
+ import { compile, obj, opt, str, unknown } from "@earendil-works/pi-schema";
14
+ import type { Static } from "@earendil-works/pi-schema";
16
15
 
17
16
  const PACKAGE_NAME = "pi-amplike";
18
17
 
19
- const ManifestSchema = Type.Object({ name: Type.String() });
18
+ const isManifest = compile(obj({ name: str() }, { open: true })).check;
20
19
 
21
- const FactoryModuleSchema = Type.Object({
22
- default: Type.Function([Type.Unknown()], Type.Unknown()),
23
- });
20
+ // JSON Schema has no function type, so these check that the export exists and
21
+ // isFunction checks that it is a function. Like TypeBox's Type.Function, the
22
+ // check can't see the function's parameters, so the caller names them.
23
+ const isFunction = <F extends (...args: never[]) => void>(
24
+ value: unknown
25
+ ): value is F => typeof value === "function";
24
26
 
25
- const ModeUtilsSchema = Type.Object({
26
- loadModeSpec: Type.Function([Type.String(), Type.String()], Type.Unknown()),
27
- });
27
+ const isFactoryModule = compile(
28
+ obj({ default: unknown() }, { open: true })
29
+ ).check;
28
30
 
29
- const ModeSpecSchema = Type.Object({
30
- modelId: Type.Optional(Type.String()),
31
- provider: Type.Optional(Type.String()),
32
- thinkingLevel: Type.Optional(Type.String()),
33
- });
31
+ const isModeUtils = compile(
32
+ obj({ loadModeSpec: unknown() }, { open: true })
33
+ ).check;
34
+
35
+ const ModeSpecSchema = obj(
36
+ {
37
+ modelId: opt(str()),
38
+ provider: opt(str()),
39
+ thinkingLevel: opt(str()),
40
+ },
41
+ { open: true }
42
+ );
43
+
44
+ const isModeSpec = compile(ModeSpecSchema).check;
34
45
 
35
46
  export type ModeSpec = Static<typeof ModeSpecSchema>;
36
47
 
@@ -53,9 +64,7 @@ const isAmplikeRoot = (root: string): boolean => {
53
64
  readFileSync(path.join(root, "package.json"), "utf-8")
54
65
  );
55
66
 
56
- return (
57
- Value.Check(ManifestSchema, manifest) && manifest.name === PACKAGE_NAME
58
- );
67
+ return isManifest(manifest) && manifest.name === PACKAGE_NAME;
59
68
  } catch {
60
69
  return false;
61
70
  }
@@ -97,12 +106,14 @@ export const findAmplike = async (
97
106
  export const loadFactory = async (file: string): Promise<ExtensionFactory> => {
98
107
  const module: unknown = await import(file);
99
108
 
100
- if (!Value.Check(FactoryModuleSchema, module)) {
109
+ if (
110
+ !isFactoryModule(module) ||
111
+ !isFunction<ExtensionFactory>(module.default)
112
+ ) {
101
113
  throw new TypeError(`${file} has no default extension factory.`);
102
114
  }
103
115
 
104
- // SAFETY: amplike's extension files default-export a Pi extension factory.
105
- return module.default as ExtensionFactory;
116
+ return module.default;
106
117
  };
107
118
 
108
119
  // amplike's handoff and subagent read -mode through this helper, so the T3
@@ -111,13 +122,15 @@ export const loadModeSpecFrom = async (root: string): Promise<LoadModeSpec> => {
111
122
  const file = path.join(root, "extensions", "lib", "mode-utils.ts");
112
123
  const module: unknown = await import(file);
113
124
 
114
- if (!Value.Check(ModeUtilsSchema, module)) {
125
+ if (!isModeUtils(module) || !isFunction<LoadModeSpec>(module.loadModeSpec)) {
115
126
  throw new TypeError(`${file} has no loadModeSpec export.`);
116
127
  }
117
128
 
129
+ const { loadModeSpec } = module;
130
+
118
131
  return async (cwd, name) => {
119
- const spec = await module.loadModeSpec(cwd, name);
132
+ const spec: unknown = await loadModeSpec(cwd, name);
120
133
 
121
- return Value.Check(ModeSpecSchema, spec) ? spec : undefined;
134
+ return isModeSpec(spec) ? spec : undefined;
122
135
  };
123
136
  };
@@ -1,22 +1,34 @@
1
- import { Type } from "typebox";
2
- import type { Static } from "typebox";
3
- import { Value } from "typebox/value";
1
+ import {
2
+ arr,
3
+ bool,
4
+ compile,
5
+ obj,
6
+ opt,
7
+ str,
8
+ union,
9
+ } from "@earendil-works/pi-schema";
10
+ import type { Static } from "@earendil-works/pi-schema";
4
11
 
5
12
  import { callT3Tool } from "../bridge/t3-http.ts";
6
13
  import type { LoadModeSpec } from "./package.ts";
7
14
 
8
- const OptionSchema = Type.Object({
9
- id: Type.String(),
10
- value: Type.Union([Type.String(), Type.Boolean()]),
11
- });
15
+ const OptionSchema = obj(
16
+ { id: str(), value: union([str(), bool()]) },
17
+ { open: true }
18
+ );
12
19
 
13
- const SelectionSchema = Type.Object({
14
- instanceId: Type.Optional(Type.String()),
15
- model: Type.String(),
16
- options: Type.Optional(Type.Array(OptionSchema)),
17
- });
20
+ const SelectionSchema = obj(
21
+ {
22
+ instanceId: opt(str()),
23
+ model: str(),
24
+ options: opt(arr(OptionSchema)),
25
+ },
26
+ { open: true }
27
+ );
18
28
 
19
- const ConfigureSchema = Type.Object({ modelSelection: SelectionSchema });
29
+ const isConfigured = compile(
30
+ obj({ modelSelection: SelectionSchema }, { open: true })
31
+ ).check;
20
32
 
21
33
  // T3's modelSelection for a Pi thread: model is "provider/modelId", and the
22
34
  // thinking level is the "thinking" option.
@@ -45,7 +57,7 @@ export const isEmptyChange = (change: SelectionChange): boolean =>
45
57
  export const currentSelection = async (): Promise<Selection> => {
46
58
  const result = await callT3Tool("t3_thread_configure", {});
47
59
 
48
- if (!Value.Check(ConfigureSchema, result)) {
60
+ if (!isConfigured(result)) {
49
61
  throw new Error("t3_thread_configure returned no modelSelection.");
50
62
  }
51
63
 
@@ -3,9 +3,19 @@ import type {
3
3
  ExtensionContext,
4
4
  ExtensionToolContext,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import { Type } from "typebox";
7
- import type { Static } from "typebox";
8
- import { Value } from "typebox/value";
6
+ import {
7
+ arr,
8
+ bool,
9
+ compile,
10
+ lit,
11
+ nullable,
12
+ num,
13
+ obj,
14
+ opt,
15
+ str,
16
+ union,
17
+ } from "@earendil-works/pi-schema";
18
+ import type { Static } from "@earendil-works/pi-schema";
9
19
 
10
20
  import { classifyFailure } from "./failures.ts";
11
21
  import type { Classified } from "./failures.ts";
@@ -62,15 +72,16 @@ const NO_RESET_MS = 60 * 60_000;
62
72
 
63
73
  const USAGE_LIMIT_TITLE = "Usage limit reached";
64
74
 
65
- const AccountEventSchema = Type.Union([
66
- Type.Object({
67
- childThreadId: Type.String(),
68
- family: Type.String(),
69
- kind: Type.Literal("delegated"),
70
- }),
71
- Type.Object({ childThreadId: Type.String(), kind: Type.Literal("settled") }),
75
+ const AccountEventSchema = union([
76
+ obj(
77
+ { childThreadId: str(), family: str(), kind: lit("delegated") },
78
+ { open: true }
79
+ ),
80
+ obj({ childThreadId: str(), kind: lit("settled") }, { open: true }),
72
81
  ]);
73
82
 
83
+ const isAccountEvent = compile(AccountEventSchema).check;
84
+
74
85
  type AccountEvent = Static<typeof AccountEventSchema>;
75
86
 
76
87
  export interface Accounts {
@@ -100,7 +111,7 @@ export const readAccounts = (ctx: ExtensionContext): Accounts => {
100
111
  if (
101
112
  entry.type === "custom" &&
102
113
  entry.customType === ACCOUNT_ENTRY &&
103
- Value.Check(AccountEventSchema, entry.data)
114
+ isAccountEvent(entry.data)
104
115
  ) {
105
116
  const event: AccountEvent = entry.data;
106
117
 
@@ -186,19 +197,23 @@ export const recordFailure = (
186
197
  updateFamily(modelFamily, record);
187
198
  };
188
199
 
189
- const ThreadReadSchema = Type.Object({
190
- hasMore: Type.Boolean(),
191
- items: Type.Array(
192
- Type.Object({
193
- text: Type.Optional(Type.Union([Type.String(), Type.Null()])),
194
- title: Type.Union([Type.String(), Type.Null()]),
195
- type: Type.String(),
196
- })
197
- ),
198
- nextPosition: Type.Union([Type.Number(), Type.Null()]),
199
- recentRuns: Type.Optional(Type.Array(Type.Object({ status: Type.String() }))),
200
- thread: Type.Object({ status: Type.String() }),
201
- });
200
+ const ThreadReadSchema = obj(
201
+ {
202
+ hasMore: bool(),
203
+ items: arr(
204
+ obj(
205
+ { text: opt(nullable(str())), title: nullable(str()), type: str() },
206
+ { open: true }
207
+ )
208
+ ),
209
+ nextPosition: nullable(num()),
210
+ recentRuns: opt(arr(obj({ status: str() }, { open: true }))),
211
+ thread: obj({ status: str() }, { open: true }),
212
+ },
213
+ { open: true }
214
+ );
215
+
216
+ const threadRead = compile(ThreadReadSchema);
202
217
 
203
218
  type ThreadRead = Static<typeof ThreadReadSchema>;
204
219
 
@@ -217,16 +232,14 @@ const bridgeReader =
217
232
  async (params) => {
218
233
  const outcome = await ctx.executeTool(toolName, params);
219
234
 
220
- return outcome.isError
221
- ? undefined
222
- : t3Json(ThreadReadSchema, outcome.result);
235
+ return outcome.isError ? undefined : t3Json(threadRead, outcome.result);
223
236
  };
224
237
 
225
238
  // A tool_result handler gets no ctx.executeTool, so it reads through T3 directly.
226
239
  export const httpReader: PageReader = async (params) => {
227
240
  const read = await callT3Tool("t3_thread_read", { ...params });
228
241
 
229
- return Value.Check(ThreadReadSchema, read) ? read : undefined;
242
+ return threadRead.check(read) ? read : undefined;
230
243
  };
231
244
 
232
245
  type ChildOutcome =
@@ -358,27 +371,33 @@ const STATUS_TOOLS: ReadonlySet<string> = new Set([
358
371
  "mcp__t3__task_status",
359
372
  ]);
360
373
 
361
- const StatusSchema = Type.Object({
362
- childThreadId: Type.String(),
363
- status: Type.Optional(Type.String()),
364
- workState: Type.Optional(Type.String()),
365
- });
374
+ const taskStatus = compile(
375
+ obj(
376
+ { childThreadId: str(), status: opt(str()), workState: opt(str()) },
377
+ { open: true }
378
+ )
379
+ );
366
380
 
367
- const DelegateInputSchema = Type.Object({
368
- target: Type.Optional(Type.Object({ model: Type.String() })),
369
- });
381
+ const isDelegateInput = compile(
382
+ obj({ target: opt(obj({ model: str() }, { open: true })) }, { open: true })
383
+ ).check;
370
384
 
371
- const DelegateResultSchema = Type.Object({ childThreadId: Type.String() });
385
+ const delegateResult = compile(obj({ childThreadId: str() }, { open: true }));
372
386
 
373
387
  const sessionFamily = (ctx: ExtensionContext): string | undefined =>
374
388
  ctx.model === undefined ? undefined : family(ctx.model.id);
375
389
 
376
- const AssistantErrorSchema = Type.Object({
377
- errorMessage: Type.Optional(Type.String()),
378
- model: Type.String(),
379
- role: Type.Literal("assistant"),
380
- stopReason: Type.String(),
381
- });
390
+ const isAssistantError = compile(
391
+ obj(
392
+ {
393
+ errorMessage: opt(str()),
394
+ model: str(),
395
+ role: lit("assistant"),
396
+ stopReason: str(),
397
+ },
398
+ { open: true }
399
+ )
400
+ ).check;
382
401
 
383
402
  // Recovery takes any family accounts.json records, such as kimi. Marking one
384
403
  // out takes only a family of a model Pi knows, so a typo cannot add one.
@@ -410,7 +429,7 @@ export const registerAccounts = (pi: ExtensionAPI): void => {
410
429
  return;
411
430
  }
412
431
 
413
- const status = t3Json(StatusSchema, event);
432
+ const status = t3Json(taskStatus, event);
414
433
 
415
434
  const childFamily =
416
435
  status === undefined
@@ -437,11 +456,9 @@ export const registerAccounts = (pi: ExtensionAPI): void => {
437
456
  return;
438
457
  }
439
458
 
440
- const result = t3Json(DelegateResultSchema, event);
459
+ const result = t3Json(delegateResult, event);
441
460
 
442
- const input = Value.Check(DelegateInputSchema, event.input)
443
- ? event.input
444
- : undefined;
461
+ const input = isDelegateInput(event.input) ? event.input : undefined;
445
462
 
446
463
  const childFamily =
447
464
  input?.target === undefined
@@ -493,7 +510,7 @@ export const registerAccounts = (pi: ExtensionAPI): void => {
493
510
  (message) => (message as { role?: string }).role === "assistant"
494
511
  );
495
512
 
496
- if (!Value.Check(AssistantErrorSchema, last)) {
513
+ if (!isAssistantError(last)) {
497
514
  lastRun = undefined;
498
515
  } else if (last.stopReason === "error") {
499
516
  lastRun = {