@wrenai/genbi 0.0.4 → 0.0.6

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 (234) hide show
  1. package/README.md +65 -3
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +43 -45
  93. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  94. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  95. package/dist-server/harness/route/describe.js +45 -6
  96. package/dist-server/harness/route/dispatched.d.ts +20 -0
  97. package/dist-server/harness/route/dispatched.js +122 -24
  98. package/dist-server/harness/route/host-query.d.ts +93 -0
  99. package/dist-server/harness/route/host-query.js +257 -0
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +8 -2
  103. package/dist-server/harness/route/types.d.ts +47 -1
  104. package/dist-server/harness/route/wren-host-proxy.d.ts +33 -0
  105. package/dist-server/harness/route/wren-host-proxy.js +228 -0
  106. package/dist-server/harness/session/run.js +30 -10
  107. package/dist-server/harness/setup/runner.d.ts +17 -3
  108. package/dist-server/harness/setup/runner.js +272 -89
  109. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  110. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  111. package/dist-server/harness/tools/setup-native.d.ts +7 -0
  112. package/dist-server/harness/tools/setup-native.js +7 -4
  113. package/dist-server/server/adopt.js +1 -17
  114. package/dist-server/server/app.d.ts +1 -0
  115. package/dist-server/server/app.js +175 -17
  116. package/dist-server/server/auth-choice.d.ts +13 -1
  117. package/dist-server/server/auth-choice.js +66 -1
  118. package/dist-server/server/bin.js +185 -25
  119. package/dist-server/server/codex-native-session.d.ts +25 -0
  120. package/dist-server/server/codex-native-session.js +1 -0
  121. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  122. package/dist-server/server/codex-runtime-composition.js +250 -0
  123. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  124. package/dist-server/server/codex-structured-ask.js +102 -0
  125. package/dist-server/server/conn-config.js +4 -16
  126. package/dist-server/server/conversation-socket.d.ts +16 -0
  127. package/dist-server/server/conversation-socket.js +65 -0
  128. package/dist-server/server/db.d.ts +46 -0
  129. package/dist-server/server/db.js +88 -9
  130. package/dist-server/server/env-detect.d.ts +6 -0
  131. package/dist-server/server/env-detect.js +7 -0
  132. package/dist-server/server/fold.d.ts +23 -4
  133. package/dist-server/server/fold.js +42 -5
  134. package/dist-server/server/harness.d.ts +2 -2
  135. package/dist-server/server/harness.js +33 -7
  136. package/dist-server/server/host-evidence-grounding.d.ts +21 -0
  137. package/dist-server/server/host-evidence-grounding.js +93 -0
  138. package/dist-server/server/interactive-terminal.d.ts +10 -6
  139. package/dist-server/server/interactive-terminal.js +33 -8
  140. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  141. package/dist-server/server/managed-wren-runtime.js +670 -0
  142. package/dist-server/server/native-artifacts.d.ts +112 -7
  143. package/dist-server/server/native-artifacts.js +194 -14
  144. package/dist-server/server/native-component-context.d.ts +19 -0
  145. package/dist-server/server/native-component-context.js +76 -0
  146. package/dist-server/server/native-component-preparation.d.ts +20 -0
  147. package/dist-server/server/native-component-preparation.js +129 -0
  148. package/dist-server/server/native-component-runtime.d.ts +36 -0
  149. package/dist-server/server/native-component-runtime.js +92 -0
  150. package/dist-server/server/native-components.d.ts +84 -0
  151. package/dist-server/server/native-components.js +160 -0
  152. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  153. package/dist-server/server/native-dispatch-process.js +65 -0
  154. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  155. package/dist-server/server/native-runtime-spec.js +169 -0
  156. package/dist-server/server/native-sessions.d.ts +172 -19
  157. package/dist-server/server/native-sessions.js +839 -244
  158. package/dist-server/server/native-shutdown.d.ts +6 -0
  159. package/dist-server/server/native-shutdown.js +28 -0
  160. package/dist-server/server/native-wren-home.d.ts +18 -0
  161. package/dist-server/server/native-wren-home.js +262 -0
  162. package/dist-server/server/node-pty-host.d.ts +2 -1
  163. package/dist-server/server/node-pty-host.js +3 -2
  164. package/dist-server/server/profile-registry.d.ts +139 -0
  165. package/dist-server/server/profile-registry.js +620 -0
  166. package/dist-server/server/runtime-binding.d.ts +4 -1
  167. package/dist-server/server/runtime-binding.js +50 -2
  168. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  169. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  170. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  171. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  172. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  173. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  174. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  175. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  176. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  177. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  178. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  179. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  180. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  181. package/dist-server/server/runtime-host/codex-events.js +66 -0
  182. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  183. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  184. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  185. package/dist-server/server/runtime-host/codex-process.js +96 -0
  186. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  187. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  188. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  189. package/dist-server/server/runtime-host/codex-session.js +492 -0
  190. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  191. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  192. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  193. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  194. package/dist-server/server/runtime-host/index.d.ts +5 -0
  195. package/dist-server/server/runtime-host/index.js +3 -0
  196. package/dist-server/server/runtime-host/local.d.ts +29 -0
  197. package/dist-server/server/runtime-host/local.js +90 -0
  198. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  199. package/dist-server/server/runtime-host/policy.js +78 -0
  200. package/dist-server/server/runtime-host/types.d.ts +52 -0
  201. package/dist-server/server/runtime-host/types.js +62 -0
  202. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  203. package/dist-server/server/structured-query-evidence.js +47 -0
  204. package/dist-server/server/structured-runtime.d.ts +52 -0
  205. package/dist-server/server/structured-runtime.js +130 -0
  206. package/dist-server/server/structured-scope.d.ts +4 -0
  207. package/dist-server/server/structured-scope.js +12 -0
  208. package/dist-server/server/turn.d.ts +6 -1
  209. package/dist-server/server/turn.js +86 -26
  210. package/dist-server/server/wire-types.d.ts +67 -3
  211. package/dist-server/server/wren-profiles.js +1 -1
  212. package/managed-wren/README.md +60 -0
  213. package/managed-wren/dependency-wheels.json +282 -0
  214. package/managed-wren/manifest.json +373 -0
  215. package/managed-wren/release-inputs.json +45 -0
  216. package/managed-wren/runtime-tree.cjs +115 -0
  217. package/managed-wren/source-inputs.json +40 -0
  218. package/package.json +15 -6
  219. package/profiles/genbi-default/context/binding.yml +17 -7
  220. package/profiles/genbi-default/context/context.json +146 -0
  221. package/profiles/genbi-default/ir.golden.json +39 -43
  222. package/profiles/genbi-default/profile.yml +31 -0
  223. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  224. package/profiles/genbi-enrich-context/context/context.json +146 -0
  225. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  226. package/profiles/genbi-monitor/context/binding.yml +13 -5
  227. package/profiles/genbi-monitor/context/context.json +146 -0
  228. package/profiles/genbi-monitor/ir.golden.json +13 -7
  229. package/profiles/genbi-report/context/binding.yml +19 -0
  230. package/profiles/genbi-report/context/context.json +223 -0
  231. package/profiles/genbi-report/ir.golden.json +328 -0
  232. package/profiles/genbi-report/profile.yml +38 -0
  233. package/profiles/genbi-setup/ir.golden.json +3 -1
  234. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -0,0 +1,492 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { CODEX_BASELINE_VERSION } from "./codex-compatibility.js";
4
+ import { codexAccountUpdatedSchema, codexRateLimitsUpdatedSchema, codexThreadSchema, codexTurnSchema, parseCodexEvent } from "./codex-events.js";
5
+ import { CodexRpcClient, CodexRpcError, object } from "./codex-rpc.js";
6
+ import { isDeepStrictEqual } from "node:util";
7
+ const sizeSchema = z.object({ cols: z.number().int().min(1).max(500), rows: z.number().int().min(1).max(500) }).strict();
8
+ const commandSchema = z.object({
9
+ command: z.array(z.string().max(65_536).refine((v) => !v.includes("\0"))).min(1).max(256),
10
+ tty: z.boolean().default(false), size: sizeSchema.optional(),
11
+ timeoutMs: z.number().int().min(1).max(300_000).default(30_000),
12
+ }).strict().refine((v) => !v.size || v.tty);
13
+ const commandResultSchema = z.object({ exitCode: z.number().int(), stdout: z.literal(""), stderr: z.literal("") });
14
+ function parse(schema, value) {
15
+ const result = schema.safeParse(value);
16
+ if (!result.success)
17
+ throw new CodexRpcError("protocol");
18
+ return result.data;
19
+ }
20
+ // Config/read expands optional TOML fields into nulls. Null is absence, not
21
+ // permission; every non-null profile/network/environment field must match.
22
+ function configured(value) {
23
+ if (Array.isArray(value))
24
+ return value.map(configured);
25
+ if (object(value))
26
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== null).map(([key, item]) => [key, configured(item)]));
27
+ return value;
28
+ }
29
+ /**
30
+ * Internal protocol driver. It has no spawn, readiness or certification grant;
31
+ * only CodexAppServerBackend may compose it with a real process. Tests can use
32
+ * an in-memory transport without creating an activation path.
33
+ */
34
+ export class CodexSession {
35
+ policy;
36
+ revalidate;
37
+ emit;
38
+ rpc;
39
+ tools;
40
+ toolsClosing;
41
+ threadId;
42
+ startingThread = false;
43
+ threadEventId;
44
+ turn;
45
+ commands = new Map();
46
+ ready = false;
47
+ accountResponseReceived = false;
48
+ closed = false;
49
+ closePromise;
50
+ constructor(transport, policy, revalidate, emit, tools) {
51
+ this.policy = policy;
52
+ this.revalidate = revalidate;
53
+ this.emit = emit;
54
+ this.tools = tools ? Object.freeze({ accountEmail: tools.accountEmail, definitions: structuredClone(tools.definitions),
55
+ assertCurrent: tools.assertCurrent.bind(tools), call: tools.call.bind(tools), close: tools.close.bind(tools) }) : undefined;
56
+ this.rpc = new CodexRpcClient(transport, ({ method, params }) => {
57
+ if (method === "account/rateLimits/updated") {
58
+ parse(codexRateLimitsUpdatedSchema, params);
59
+ return;
60
+ } // host-only telemetry
61
+ if (method === "account/updated") {
62
+ if (this.accountResponseReceived || !this.tools)
63
+ this.protocolFailure();
64
+ parse(codexAccountUpdatedSchema, params);
65
+ return; // account/read still pins identity before ready; never project auth to UI
66
+ }
67
+ this.receive(parseCodexEvent(method, params));
68
+ }, 1_048_576, this.tools ? (request) => this.callTool(request.method, request.params) : undefined);
69
+ this.rpc.onFailure((error) => {
70
+ this.closed = true;
71
+ if (this.turn)
72
+ this.settleTurn(undefined, error);
73
+ this.commands.clear();
74
+ void this.closeTools().catch(() => { });
75
+ });
76
+ }
77
+ static async connect(transport, policy, revalidate, emit, created, tools) {
78
+ const session = new CodexSession(transport, policy, revalidate, emit, tools);
79
+ try {
80
+ created?.(session);
81
+ session.tools?.assertCurrent();
82
+ const initialized = parse(z.object({ codexHome: z.literal(policy.codexHome), platformFamily: z.literal("unix"), platformOs: z.literal("macos"), userAgent: z.string().min(1) }), await session.rpc.request("initialize", {
83
+ clientInfo: { name: "genbi", version: "0.0.4" }, capabilities: { experimentalApi: true },
84
+ }));
85
+ if (!initialized.userAgent.includes(`/${CODEX_BASELINE_VERSION} `))
86
+ throw new CodexRpcError("protocol");
87
+ session.rpc.notify("initialized", {});
88
+ const effective = await session.rpc.request("config/read", { cwd: policy.cwd, includeLayers: false });
89
+ if (!object(effective) || !object(effective.config))
90
+ throw new CodexRpcError("protocol");
91
+ for (const [key, expected] of Object.entries(policy.configuration)) {
92
+ const actual = effective.config[key];
93
+ if (key === "features") {
94
+ if (!object(actual) || !object(expected) || Object.entries(expected).some(([name, value]) => !isDeepStrictEqual(actual[name], value)))
95
+ throw new CodexRpcError("protocol");
96
+ }
97
+ else if (!isDeepStrictEqual(configured(actual), configured(expected)))
98
+ throw new CodexRpcError("protocol");
99
+ }
100
+ const profiles = parse(z.object({ data: z.array(z.object({ id: z.string(), allowed: z.boolean() })), nextCursor: z.null().optional() }), await session.rpc.request("permissionProfile/list", { cwd: policy.cwd }));
101
+ if (profiles.data.filter((p) => p.id === policy.profile && p.allowed).length !== 1)
102
+ throw new CodexRpcError("permission");
103
+ if (session.closed)
104
+ throw new CodexRpcError("closed");
105
+ if (session.tools) {
106
+ parse(z.object({ requiresOpenaiAuth: z.literal(true), account: z.object({ type: z.literal("chatgpt"), email: z.literal(session.tools.accountEmail) }) }), await session.rpc.request("account/read", { refreshToken: false }, 10_000, undefined, () => { session.accountResponseReceived = true; }));
107
+ if (session.closed)
108
+ throw new CodexRpcError("protocol");
109
+ session.tools.assertCurrent();
110
+ }
111
+ session.ready = true;
112
+ return session;
113
+ }
114
+ catch (error) {
115
+ await session.close();
116
+ throw error;
117
+ }
118
+ }
119
+ check() {
120
+ if (this.closed || !this.ready)
121
+ throw new CodexRpcError("closed");
122
+ try {
123
+ this.revalidate();
124
+ this.tools?.assertCurrent();
125
+ }
126
+ catch {
127
+ this.rpc.fail("protocol");
128
+ throw new CodexRpcError("protocol");
129
+ }
130
+ }
131
+ protocolFailure() { this.rpc.fail("protocol"); throw new CodexRpcError("protocol"); }
132
+ async startThread() {
133
+ this.check();
134
+ if (this.threadId || this.startingThread)
135
+ throw new CodexRpcError("protocol");
136
+ this.startingThread = true;
137
+ try {
138
+ const result = parse(z.object({ thread: codexThreadSchema }), await this.rpc.request("thread/start", {
139
+ cwd: this.policy.cwd, permissions: this.policy.profile, approvalPolicy: "never", ephemeral: true,
140
+ ...(this.policy.model ? { model: this.policy.model } : {}),
141
+ runtimeWorkspaceRoots: this.tools ? [] : [this.policy.cwd], environments: [], dynamicTools: this.tools?.definitions ?? [],
142
+ ...(this.tools ? { baseInstructions: "Use the provided tool for the selected analytical request. Component steps, data access and persistence are owned by the host.", developerInstructions: "" } : {}),
143
+ allowProviderModelFallback: false,
144
+ }));
145
+ this.validateThread(result.thread);
146
+ if (this.threadEventId && result.thread.id !== this.threadEventId)
147
+ this.protocolFailure();
148
+ this.threadId = result.thread.id;
149
+ return result.thread.id;
150
+ }
151
+ catch (error) {
152
+ this.rpc.fail("protocol");
153
+ throw error;
154
+ }
155
+ finally {
156
+ this.startingThread = false;
157
+ }
158
+ }
159
+ validateThread(thread) {
160
+ if (thread.cwd !== this.policy.cwd || thread.cliVersion !== CODEX_BASELINE_VERSION || !thread.ephemeral)
161
+ this.protocolFailure();
162
+ }
163
+ /** Completes on turn/completed, not the immediate turn/start acknowledgement. */
164
+ async runTurn(text, options = {}) {
165
+ this.check();
166
+ if (!this.threadId || this.turn || typeof text !== "string" || text.length === 0 || Buffer.byteLength(text) > 262_144)
167
+ throw new CodexRpcError("protocol");
168
+ const timeoutMs = options.timeoutMs ?? 120_000;
169
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000)
170
+ throw new CodexRpcError("protocol");
171
+ if (options.signal?.aborted) {
172
+ await this.close();
173
+ throw new CodexRpcError("cancelled");
174
+ }
175
+ const abort = () => this.rpc.fail("cancelled");
176
+ let active;
177
+ let releaseTools;
178
+ const ready = new Promise((resolve) => { releaseTools = resolve; });
179
+ const completion = new Promise((resolve, reject) => {
180
+ active = { resolve, reject, started: false, early: [], earlyBytes: 0, items: new Map(),
181
+ toolsAbort: new AbortController(), calls: new Set(), pendingTools: 0, ready, releaseTools,
182
+ timer: setTimeout(() => this.rpc.fail("timeout"), timeoutMs),
183
+ detach: () => options.signal?.removeEventListener("abort", abort),
184
+ };
185
+ });
186
+ this.turn = active;
187
+ // Install rejection handling before a synchronous transport can fail.
188
+ void completion.catch(() => { });
189
+ options.signal?.addEventListener("abort", abort, { once: true });
190
+ try {
191
+ const result = parse(z.object({ turn: codexTurnSchema }), await this.rpc.request("turn/start", {
192
+ threadId: this.threadId, input: [{ type: "text", text }], cwd: this.policy.cwd,
193
+ permissions: this.policy.profile, approvalPolicy: "never", runtimeWorkspaceRoots: this.tools ? [] : [this.policy.cwd], environments: [],
194
+ }, Math.min(timeoutMs, 10_000)));
195
+ if (this.turn !== active || result.turn.status !== "inProgress")
196
+ this.protocolFailure();
197
+ active.id = result.turn.id;
198
+ const queued = active.early;
199
+ active.early = [];
200
+ active.earlyBytes = 0;
201
+ for (const event of queued)
202
+ this.receive(event);
203
+ active.releaseTools();
204
+ return await completion;
205
+ }
206
+ catch (error) {
207
+ this.rpc.fail(error instanceof CodexRpcError ? error.reason : "protocol");
208
+ await this.close();
209
+ throw error instanceof CodexRpcError ? error : new CodexRpcError("protocol");
210
+ }
211
+ }
212
+ async steerTurn(text, expectedTurnId) {
213
+ this.check();
214
+ const active = this.turn;
215
+ if (!active?.id || active.id !== expectedTurnId || active.interruptRequested || !text.trim() || Buffer.byteLength(text) > 262_144)
216
+ throw new CodexRpcError("protocol");
217
+ const result = parse(z.object({ turnId: z.literal(active.id) }).strict(), await this.rpc.request("turn/steer", {
218
+ threadId: this.threadId, expectedTurnId, input: [{ type: "text", text }],
219
+ }));
220
+ if (this.turn !== active || active.interruptRequested)
221
+ throw new CodexRpcError("protocol");
222
+ return result;
223
+ }
224
+ async interruptTurn() {
225
+ this.check();
226
+ const active = this.turn;
227
+ if (!active?.id)
228
+ throw new CodexRpcError("protocol");
229
+ try {
230
+ active.interruptRequested = true;
231
+ active.toolsAbort.abort();
232
+ parse(z.object({}).strict(), await this.rpc.request("turn/interrupt", { threadId: this.threadId, turnId: active.id }, 10_000));
233
+ // An ack is not completion. Bound how long a silent peer may keep working.
234
+ if (this.turn === active) {
235
+ clearTimeout(active.timer);
236
+ active.timer = setTimeout(() => this.rpc.fail("timeout"), 10_000);
237
+ }
238
+ }
239
+ catch {
240
+ await this.close();
241
+ throw new CodexRpcError("protocol");
242
+ }
243
+ }
244
+ startCommand(input, signal) {
245
+ this.check();
246
+ if (this.tools)
247
+ throw new CodexRpcError("permission");
248
+ const command = parse(commandSchema, input);
249
+ if (this.commands.size >= 16 || !command.command[0])
250
+ throw new CodexRpcError("protocol");
251
+ const id = randomUUID();
252
+ const state = { tty: command.tty, stdinClosed: false, bytes: 0 };
253
+ this.commands.set(id, state);
254
+ const completed = this.rpc.request("command/exec", {
255
+ ...command, processId: id, cwd: this.policy.cwd, permissionProfile: this.policy.profile,
256
+ env: this.policy.commandEnvironment, streamStdin: true, streamStdoutStderr: true, outputBytesCap: 262_144,
257
+ }, command.timeoutMs + 3_000, signal).then((value) => {
258
+ const result = parse(commandResultSchema, value);
259
+ this.commands.delete(id);
260
+ return { exitCode: result.exitCode };
261
+ }).catch(async (error) => {
262
+ this.rpc.fail(error instanceof CodexRpcError ? error.reason : "protocol");
263
+ await this.close();
264
+ throw error instanceof CodexRpcError ? error : new CodexRpcError("protocol");
265
+ });
266
+ void completed.catch(() => { });
267
+ const control = async (method, params) => {
268
+ this.check();
269
+ if (!this.commands.has(id))
270
+ throw new CodexRpcError("closed");
271
+ try {
272
+ parse(z.object({}).strict(), await this.rpc.request(method, { processId: id, ...params }, 2_000));
273
+ }
274
+ catch (error) {
275
+ this.rpc.fail("protocol");
276
+ await this.close();
277
+ throw error;
278
+ }
279
+ };
280
+ return Object.freeze({ id, completed,
281
+ write: async (bytes, closeStdin = false) => {
282
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength > 65_536 || state.stdinClosed || typeof closeStdin !== "boolean")
283
+ throw new CodexRpcError("protocol");
284
+ if (closeStdin)
285
+ state.stdinClosed = true;
286
+ await control("command/exec/write", { deltaBase64: Buffer.from(bytes).toString("base64"), closeStdin });
287
+ },
288
+ resize: async (size) => {
289
+ if (!state.tty)
290
+ throw new CodexRpcError("protocol");
291
+ await control("command/exec/resize", { size: parse(sizeSchema, size) });
292
+ },
293
+ terminate: async () => {
294
+ await control("command/exec/terminate", {});
295
+ let timer;
296
+ try {
297
+ await Promise.race([completed, new Promise((_, reject) => { timer = setTimeout(() => { this.rpc.fail("timeout"); reject(new CodexRpcError("timeout")); }, 2_000); })]);
298
+ }
299
+ catch (error) {
300
+ await this.close();
301
+ throw error;
302
+ }
303
+ finally {
304
+ clearTimeout(timer);
305
+ }
306
+ },
307
+ });
308
+ }
309
+ settleTurn(value, error) {
310
+ const active = this.turn;
311
+ if (!active)
312
+ return;
313
+ if (value?.status === "interrupted" && active.pendingTools) {
314
+ active.toolsAbort.abort();
315
+ void active.toolFinished.then(() => { if (this.turn === active)
316
+ this.settleTurn(value); });
317
+ return;
318
+ }
319
+ this.turn = undefined;
320
+ clearTimeout(active.timer);
321
+ active.detach();
322
+ active.toolsAbort.abort();
323
+ active.releaseTools();
324
+ if (value)
325
+ active.resolve(value);
326
+ else
327
+ active.reject(error);
328
+ }
329
+ receive(event) {
330
+ if (this.closed)
331
+ return;
332
+ if (event.method === "remoteControl/status/changed")
333
+ return; // validated disabled; never forward host identity
334
+ if (event.method === "command/exec/outputDelta") {
335
+ const command = this.commands.get(event.params.processId);
336
+ if (!command || (command.tty && event.params.stream !== "stdout"))
337
+ this.protocolFailure();
338
+ command.bytes += Buffer.from(event.params.deltaBase64, "base64").byteLength;
339
+ if (command.bytes > 524_288)
340
+ this.protocolFailure();
341
+ this.emit(event);
342
+ return;
343
+ }
344
+ if (event.method === "thread/started") {
345
+ this.validateThread(event.params.thread);
346
+ if (this.threadEventId || (!this.startingThread && this.threadId !== event.params.thread.id))
347
+ this.protocolFailure();
348
+ this.threadEventId = event.params.thread.id;
349
+ return;
350
+ }
351
+ if (!this.threadId || event.params.threadId !== this.threadId)
352
+ this.protocolFailure();
353
+ if (event.method === "warning") {
354
+ const features = this.policy.configuration.features;
355
+ if (!this.tools || !object(features) || features.code_mode_host !== false || features.code_mode !== false)
356
+ this.protocolFailure();
357
+ return; // validated disabled capability; never forward raw vendor warnings
358
+ }
359
+ if (event.method === "thread/status/changed") {
360
+ this.emit(event);
361
+ return;
362
+ }
363
+ const active = this.turn;
364
+ if (!active)
365
+ this.protocolFailure();
366
+ if (this.tools && "item" in event.params && !["userMessage", "agentMessage", "reasoning", "dynamicToolCall", "contextCompaction"].includes(event.params.item.type))
367
+ this.protocolFailure();
368
+ if (!active.id) {
369
+ active.earlyBytes += Buffer.byteLength(JSON.stringify(event));
370
+ if (active.early.length >= 128 || active.earlyBytes > 1_048_576)
371
+ this.protocolFailure();
372
+ active.early.push(event);
373
+ return;
374
+ }
375
+ const turnId = "turn" in event.params ? event.params.turn.id : event.params.turnId;
376
+ if (turnId !== active.id)
377
+ this.protocolFailure();
378
+ if (event.method === "turn/started") {
379
+ if (active.started || event.params.turn.status !== "inProgress")
380
+ this.protocolFailure();
381
+ active.started = true;
382
+ }
383
+ else if (event.method === "turn/completed") {
384
+ // Full integrity validation before terminal output is emitted or settled.
385
+ this.check();
386
+ if (event.params.turn.status === "inProgress" || (active.pendingTools && event.params.turn.status === "completed"))
387
+ this.protocolFailure();
388
+ if (this.tools && event.params.turn.status === "completed") {
389
+ const returned = new Set();
390
+ for (const item of event.params.turn.items) {
391
+ const observed = active.items.get(item.id);
392
+ if (!observed?.completed || observed.type !== item.type || !isDeepStrictEqual(observed.item, item) || returned.has(item.id))
393
+ this.protocolFailure();
394
+ returned.add(item.id);
395
+ }
396
+ if (!active.started || [...active.items.values()].some((item) => !item.completed)
397
+ || ((event.params.turn.itemsView ?? "full") === "full" && returned.size !== active.items.size))
398
+ this.protocolFailure();
399
+ if (event.params.turn.itemsView === "summary" || event.params.turn.itemsView === "notLoaded") {
400
+ const complete = { ...event.params.turn, itemsView: "full", items: [...active.items.values()].map((item) => item.item) };
401
+ this.emit({ ...event, params: { ...event.params, turn: complete } });
402
+ this.settleTurn(complete);
403
+ return;
404
+ }
405
+ }
406
+ this.emit(event);
407
+ this.settleTurn(event.params.turn);
408
+ return;
409
+ }
410
+ else if (event.method === "item/started") {
411
+ const received = event.params.item;
412
+ if (received.type === "dynamicToolCall" && (!this.tools || !this.tools.definitions.some((tool) => tool.name === received.tool)))
413
+ this.protocolFailure();
414
+ if (active.items.has(event.params.item.id) || active.items.size >= 512)
415
+ this.protocolFailure();
416
+ active.items.set(event.params.item.id, { type: event.params.item.type, completed: false });
417
+ }
418
+ else if (event.method === "item/completed") {
419
+ const item = active.items.get(event.params.item.id);
420
+ if (!item || item.completed || item.type !== event.params.item.type)
421
+ this.protocolFailure();
422
+ if (event.params.item.type === "dynamicToolCall" && (!active.calls.has(event.params.item.id) || active.pendingTools))
423
+ this.protocolFailure();
424
+ item.completed = true;
425
+ item.item = structuredClone(event.params.item);
426
+ }
427
+ else if ("itemId" in event.params) {
428
+ const item = active.items.get(event.params.itemId);
429
+ if (!item || item.completed)
430
+ this.protocolFailure();
431
+ }
432
+ this.emit(event);
433
+ }
434
+ async callTool(method, params) {
435
+ this.check();
436
+ const call = parse(z.object({ threadId: z.string().min(1), turnId: z.string().min(1), callId: z.string().min(1).max(256),
437
+ tool: z.string().min(1), arguments: z.unknown(), namespace: z.null().optional() }).strict(), params);
438
+ const active = this.turn;
439
+ if (method !== "item/tool/call" || !this.tools || !active || call.threadId !== this.threadId
440
+ || !this.tools.definitions.some((tool) => tool.name === call.tool) || active.calls.has(call.callId)
441
+ || active.calls.size >= 32 || active.pendingTools || Buffer.byteLength(JSON.stringify(call.arguments) ?? "") > 65_536)
442
+ this.protocolFailure();
443
+ active.calls.add(call.callId);
444
+ active.pendingTools++;
445
+ let finished;
446
+ active.toolFinished = new Promise((resolve) => { finished = resolve; });
447
+ try {
448
+ await active.ready;
449
+ this.check();
450
+ active.toolsAbort.signal.throwIfAborted();
451
+ const item = active.items.get(call.callId);
452
+ if (this.turn !== active || call.turnId !== active.id || !active.started || item?.type !== "dynamicToolCall" || item.completed)
453
+ this.protocolFailure();
454
+ const result = await this.tools.call(call.tool, call.arguments, `${active.id}:${call.callId}`, active.toolsAbort.signal);
455
+ this.check();
456
+ active.toolsAbort.signal.throwIfAborted();
457
+ if (this.turn !== active)
458
+ this.protocolFailure();
459
+ const text = JSON.stringify(result);
460
+ if (text === undefined || Buffer.byteLength(text) > 1_048_000)
461
+ this.protocolFailure();
462
+ return { contentItems: [{ type: "inputText", text }], success: true };
463
+ }
464
+ catch (error) {
465
+ // A requested cancellation is a failed tool result, not malformed RPC.
466
+ // Recheck the session scope and integrity before keeping it reusable.
467
+ if (!active.interruptRequested || !active.toolsAbort.signal.aborted || error !== active.toolsAbort.signal.reason)
468
+ throw error;
469
+ this.check();
470
+ return { contentItems: [{ type: "inputText", text: "Operation cancelled." }], success: false };
471
+ }
472
+ finally {
473
+ active.pendingTools--;
474
+ finished();
475
+ }
476
+ }
477
+ closeTools() {
478
+ return this.toolsClosing ??= Promise.resolve().then(() => this.tools?.close()).catch(() => { throw new CodexRpcError("cleanup"); });
479
+ }
480
+ /** Disconnect and BFF shutdown use the same bounded, connection-owned close. */
481
+ onFailure(listener) {
482
+ return this.rpc.onFailure(listener);
483
+ }
484
+ close() {
485
+ this.closed = true;
486
+ this.closePromise ??= Promise.allSettled([this.rpc.close(), this.closeTools()]).then((results) => {
487
+ if (results.some((result) => result.status === "rejected"))
488
+ throw new CodexRpcError("cleanup");
489
+ });
490
+ return this.closePromise;
491
+ }
492
+ }
@@ -0,0 +1,35 @@
1
+ import type { CodexConversation } from "./codex-conversation.js";
2
+ /**
3
+ * Protocol presented to the official remote TUI, not an app-server proxy.
4
+ * Only text submission and interruption reach the governed conversation.
5
+ * Config, filesystem, shell, auth, plugin and cross-thread mutations never do.
6
+ * Catalogs describe this host's restricted surface, not the operator's account.
7
+ */
8
+ export declare class CodexTerminalProtocol {
9
+ private readonly conversation;
10
+ private readonly scope;
11
+ private readonly send;
12
+ private readonly assertActive;
13
+ private readonly onReady;
14
+ private initialized;
15
+ private acknowledged;
16
+ private started;
17
+ private submitted;
18
+ private attachment;
19
+ private pendingTurn;
20
+ private activeTurn;
21
+ private closed;
22
+ private readonly createdAt;
23
+ private readonly ids;
24
+ constructor(conversation: CodexConversation, scope: {
25
+ cwd: string;
26
+ clientHome: string;
27
+ model: string;
28
+ }, send: (message: unknown) => void, assertActive: () => void, onReady?: () => void);
29
+ close(): void;
30
+ private thread;
31
+ private result;
32
+ private error;
33
+ private event;
34
+ receive(value: unknown): Promise<void>;
35
+ }