@parall/claude-agent 1.27.0 → 1.28.1

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.
@@ -17,6 +17,7 @@ export declare function buildSpawnEnv(parentEnv: NodeJS.ProcessEnv, claudeHome:
17
17
  export declare class ClaudeCodeAdapter implements DispatchAdapter {
18
18
  private readonly opts;
19
19
  private readonly processes;
20
+ private readonly pendingInjections;
20
21
  private shuttingDown;
21
22
  private _model;
22
23
  private _effortLevel;
@@ -27,12 +28,26 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
27
28
  model?: string | null;
28
29
  effort?: string | null;
29
30
  }): void;
31
+ enqueueDuringDispatch(sessionKey: string, body: string): boolean;
32
+ hasPendingInjections(sessionKey: string): boolean;
30
33
  dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
31
34
  getBranchPoint(_sessionKey: string): string | undefined;
32
35
  getSessionHistoryPath(sessionKey: string): string | undefined;
33
36
  forkSession({ sessionKey }: ForkOpts): import("@parall/agent-core").ForkSessionHandle | null;
34
37
  cleanupFork({ fork }: CleanupForkOpts): void;
35
38
  shutdown(): Promise<void>;
39
+ private static readonly STEER_TURN_TIMEOUT_MS;
40
+ /**
41
+ * Consume a steer turn whose message was already written to stdin via
42
+ * enqueueDuringDispatch. Skip the stdin write — only read parser output.
43
+ * If no output arrives within STEER_TURN_TIMEOUT_MS, the steer was
44
+ * incorporated into the previous turn and there is nothing to consume.
45
+ *
46
+ * On timeout, the losing parser.next() promise is saved to
47
+ * state.steerReadPending so the next runTurn can drain it instead of
48
+ * silently losing the first event of the subsequent turn.
49
+ */
50
+ private consumeSteerTurn;
36
51
  private runTurn;
37
52
  private ensureProcess;
38
53
  private spawnProcess;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EACL,oBAAoB,EAErB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D,iDAAiD;IACjD,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D,CAAC;AAEF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAAC,UAAU,CAuBnB;AAgBD,qBAAa,iBAAkB,YAAW,eAAe;IAM3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;gBAEZ,IAAI,EAAE,wBAAwB;IAI3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAAwB;IAC9D,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAA8B;IAErE,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAYtE,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAuBxG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ7D,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAQ/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAShB,OAAO;IAgFtB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,SAAS;IAyCjB,OAAO,CAAC,uBAAuB;CAkBhC"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EACL,oBAAoB,EAErB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D,iDAAiD;IACjD,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D,CAAC;AAEF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAAC,UAAU,CAuBnB;AAiBD,qBAAa,iBAAkB,YAAW,eAAe;IAO3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;gBAEZ,IAAI,EAAE,wBAAwB;IAI3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAAwB;IAC9D,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAA8B;IAErE,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAY7E,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAchE,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAiCxG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ7D,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAQ/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAS/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAEvD;;;;;;;;;OASG;YACY,gBAAgB;YAsDhB,OAAO;IAwGtB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,SAAS;IAyCjB,OAAO,CAAC,uBAAuB;CAkBhC"}
package/dist/dispatch.js CHANGED
@@ -31,6 +31,7 @@ export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
31
31
  export class ClaudeCodeAdapter {
32
32
  opts;
33
33
  processes = new Map();
34
+ pendingInjections = new Map();
34
35
  shuttingDown = false;
35
36
  _model;
36
37
  _effortLevel;
@@ -53,7 +54,35 @@ export class ClaudeCodeAdapter {
53
54
  }
54
55
  }
55
56
  }
57
+ enqueueDuringDispatch(sessionKey, body) {
58
+ const state = this.processes.get(sessionKey);
59
+ if (!state || state.done)
60
+ return false;
61
+ const { proc } = state.handle;
62
+ if (proc.exitCode !== null || proc.signalCode !== null || proc.stdin.destroyed)
63
+ return false;
64
+ try {
65
+ this.writeUserMessage(state.handle, body);
66
+ this.pendingInjections.set(sessionKey, (this.pendingInjections.get(sessionKey) ?? 0) + 1);
67
+ return true;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ hasPendingInjections(sessionKey) {
74
+ return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
75
+ }
56
76
  async *dispatch({ event, bodyForAgent, sessionKey, context }) {
77
+ const pending = this.pendingInjections.get(sessionKey) ?? 0;
78
+ if (pending > 0) {
79
+ this.pendingInjections.delete(sessionKey);
80
+ context.log?.info?.(`claude-agent[${context.accountId}]: consuming ${pending} steer turn(s)`);
81
+ for (let i = 0; i < pending; i++) {
82
+ yield* this.consumeSteerTurn(sessionKey, context.log);
83
+ }
84
+ return;
85
+ }
57
86
  let promptBody = bodyForAgent;
58
87
  let releasePreparedAttachments = () => { };
59
88
  try {
@@ -103,6 +132,66 @@ export class ClaudeCodeAdapter {
103
132
  this.processes.clear();
104
133
  await this.opts.sessionManager.shutdownAll();
105
134
  }
135
+ static STEER_TURN_TIMEOUT_MS = 10_000;
136
+ /**
137
+ * Consume a steer turn whose message was already written to stdin via
138
+ * enqueueDuringDispatch. Skip the stdin write — only read parser output.
139
+ * If no output arrives within STEER_TURN_TIMEOUT_MS, the steer was
140
+ * incorporated into the previous turn and there is nothing to consume.
141
+ *
142
+ * On timeout, the losing parser.next() promise is saved to
143
+ * state.steerReadPending so the next runTurn can drain it instead of
144
+ * silently losing the first event of the subsequent turn.
145
+ */
146
+ async *consumeSteerTurn(sessionKey, log) {
147
+ const state = this.processes.get(sessionKey);
148
+ if (!state || state.done) {
149
+ throw new Error("claude-agent: process dead during steer consumption");
150
+ }
151
+ const groupKey = randomUUID();
152
+ const parserNext = state.parser.next();
153
+ const firstRead = await Promise.race([
154
+ parserNext.then((r) => ({ kind: "value", result: r })),
155
+ new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), ClaudeCodeAdapter.STEER_TURN_TIMEOUT_MS)),
156
+ ]);
157
+ if (firstRead.kind === "timeout") {
158
+ state.steerReadPending = parserNext;
159
+ log?.info?.(`claude-agent: steer turn timeout — steer was incorporated into previous turn`);
160
+ return;
161
+ }
162
+ let next = firstRead.result;
163
+ // eslint-disable-next-line no-constant-condition
164
+ while (true) {
165
+ if (next.done) {
166
+ state.done = true;
167
+ this.processes.delete(sessionKey);
168
+ throw new Error("claude-agent: process exited while consuming steer turn");
169
+ }
170
+ const parsed = next.value;
171
+ if (parsed.type === "session_id") {
172
+ this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
173
+ yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
174
+ }
175
+ else if (parsed.type === "turn_end") {
176
+ if (state.needsRestart)
177
+ this.killProcess(sessionKey, state);
178
+ return;
179
+ }
180
+ else if (parsed.type === "error") {
181
+ yield parsed;
182
+ }
183
+ else if (parsed.type === "text") {
184
+ yield { ...parsed, project: false, groupKey };
185
+ }
186
+ else if (parsed.type === "runtime_session") {
187
+ yield parsed;
188
+ }
189
+ else {
190
+ yield { ...parsed, groupKey };
191
+ }
192
+ next = await state.parser.next();
193
+ }
194
+ }
106
195
  async *runTurn(sessionKey, promptBody, log) {
107
196
  let state;
108
197
  try {
@@ -122,6 +211,35 @@ export class ClaudeCodeAdapter {
122
211
  this.killProcess(sessionKey, state);
123
212
  return;
124
213
  }
214
+ if (state.steerReadPending) {
215
+ const orphaned = await state.steerReadPending;
216
+ state.steerReadPending = undefined;
217
+ if (!orphaned.done) {
218
+ const parsed = orphaned.value;
219
+ if (parsed.type === "session_id") {
220
+ this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
221
+ yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
222
+ }
223
+ else if (parsed.type === "turn_end") {
224
+ if (state.needsRestart)
225
+ this.killProcess(sessionKey, state);
226
+ return;
227
+ }
228
+ else if (parsed.type === "error") {
229
+ sawError = true;
230
+ yield parsed;
231
+ }
232
+ else if (parsed.type === "text") {
233
+ yield { ...parsed, project: false, groupKey };
234
+ }
235
+ else if (parsed.type === "runtime_session") {
236
+ yield parsed;
237
+ }
238
+ else {
239
+ yield { ...parsed, groupKey };
240
+ }
241
+ }
242
+ }
125
243
  while (true) {
126
244
  const next = await state.parser.next();
127
245
  if (next.done) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/claude-agent",
3
- "version": "1.27.0",
3
+ "version": "1.28.1",
4
4
  "description": "Claude Code bridge runtime for self-hosted Parall agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,9 +25,9 @@
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
- "@parall/cli": "1.27.0",
29
- "@parall/sdk": "1.27.0",
30
- "@parall/agent-core": "1.27.0"
28
+ "@parall/agent-core": "1.28.1",
29
+ "@parall/sdk": "1.28.1",
30
+ "@parall/cli": "1.28.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.0.0",
package/src/dispatch.ts CHANGED
@@ -85,10 +85,12 @@ type ProcessState = {
85
85
  parser: AsyncGenerator<ClaudeParsedEvent>;
86
86
  done: boolean;
87
87
  needsRestart: boolean;
88
+ steerReadPending?: Promise<IteratorResult<ClaudeParsedEvent>>;
88
89
  };
89
90
 
90
91
  export class ClaudeCodeAdapter implements DispatchAdapter {
91
92
  private readonly processes = new Map<string, ProcessState>();
93
+ private readonly pendingInjections = new Map<string, number>();
92
94
  private shuttingDown = false;
93
95
  private _model: string | undefined;
94
96
  private _effortLevel: string | undefined;
@@ -112,7 +114,35 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
112
114
  }
113
115
  }
114
116
 
117
+ enqueueDuringDispatch(sessionKey: string, body: string): boolean {
118
+ const state = this.processes.get(sessionKey);
119
+ if (!state || state.done) return false;
120
+ const { proc } = state.handle;
121
+ if (proc.exitCode !== null || proc.signalCode !== null || proc.stdin.destroyed) return false;
122
+ try {
123
+ this.writeUserMessage(state.handle, body);
124
+ this.pendingInjections.set(sessionKey, (this.pendingInjections.get(sessionKey) ?? 0) + 1);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ hasPendingInjections(sessionKey: string): boolean {
132
+ return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
133
+ }
134
+
115
135
  async *dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
136
+ const pending = this.pendingInjections.get(sessionKey) ?? 0;
137
+ if (pending > 0) {
138
+ this.pendingInjections.delete(sessionKey);
139
+ context.log?.info?.(`claude-agent[${context.accountId}]: consuming ${pending} steer turn(s)`);
140
+ for (let i = 0; i < pending; i++) {
141
+ yield* this.consumeSteerTurn(sessionKey, context.log);
142
+ }
143
+ return;
144
+ }
145
+
116
146
  let promptBody = bodyForAgent;
117
147
  let releasePreparedAttachments = () => {};
118
148
  try {
@@ -168,6 +198,72 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
168
198
  await this.opts.sessionManager.shutdownAll();
169
199
  }
170
200
 
201
+ private static readonly STEER_TURN_TIMEOUT_MS = 10_000;
202
+
203
+ /**
204
+ * Consume a steer turn whose message was already written to stdin via
205
+ * enqueueDuringDispatch. Skip the stdin write — only read parser output.
206
+ * If no output arrives within STEER_TURN_TIMEOUT_MS, the steer was
207
+ * incorporated into the previous turn and there is nothing to consume.
208
+ *
209
+ * On timeout, the losing parser.next() promise is saved to
210
+ * state.steerReadPending so the next runTurn can drain it instead of
211
+ * silently losing the first event of the subsequent turn.
212
+ */
213
+ private async *consumeSteerTurn(
214
+ sessionKey: string,
215
+ log: GatewayLogger | undefined,
216
+ ): AsyncGenerator<RuntimeEvent> {
217
+ const state = this.processes.get(sessionKey);
218
+ if (!state || state.done) {
219
+ throw new Error("claude-agent: process dead during steer consumption");
220
+ }
221
+
222
+ const groupKey = randomUUID();
223
+
224
+ const parserNext = state.parser.next();
225
+ const firstRead = await Promise.race([
226
+ parserNext.then((r) => ({ kind: "value" as const, result: r })),
227
+ new Promise<{ kind: "timeout" }>((resolve) =>
228
+ setTimeout(() => resolve({ kind: "timeout" }), ClaudeCodeAdapter.STEER_TURN_TIMEOUT_MS),
229
+ ),
230
+ ]);
231
+
232
+ if (firstRead.kind === "timeout") {
233
+ state.steerReadPending = parserNext;
234
+ log?.info?.(`claude-agent: steer turn timeout — steer was incorporated into previous turn`);
235
+ return;
236
+ }
237
+
238
+ let next = firstRead.result;
239
+ // eslint-disable-next-line no-constant-condition
240
+ while (true) {
241
+ if (next.done) {
242
+ state.done = true;
243
+ this.processes.delete(sessionKey);
244
+ throw new Error("claude-agent: process exited while consuming steer turn");
245
+ }
246
+ const parsed = next.value;
247
+
248
+ if (parsed.type === "session_id") {
249
+ this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
250
+ yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
251
+ } else if (parsed.type === "turn_end") {
252
+ if (state.needsRestart) this.killProcess(sessionKey, state);
253
+ return;
254
+ } else if (parsed.type === "error") {
255
+ yield parsed;
256
+ } else if (parsed.type === "text") {
257
+ yield { ...parsed, project: false, groupKey };
258
+ } else if (parsed.type === "runtime_session") {
259
+ yield parsed;
260
+ } else {
261
+ yield { ...parsed, groupKey };
262
+ }
263
+ next = await state.parser.next();
264
+ }
265
+ }
266
+
171
267
  private async *runTurn(
172
268
  sessionKey: string,
173
269
  promptBody: string,
@@ -191,6 +287,30 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
191
287
  return;
192
288
  }
193
289
 
290
+ if (state.steerReadPending) {
291
+ const orphaned = await state.steerReadPending;
292
+ state.steerReadPending = undefined;
293
+ if (!orphaned.done) {
294
+ const parsed = orphaned.value;
295
+ if (parsed.type === "session_id") {
296
+ this.opts.sessionManager.recordSessionId(sessionKey, parsed.sessionId);
297
+ yield { type: "runtime_session", runtimeSessionId: parsed.sessionId, runtimeLaneKey: sessionKey };
298
+ } else if (parsed.type === "turn_end") {
299
+ if (state.needsRestart) this.killProcess(sessionKey, state);
300
+ return;
301
+ } else if (parsed.type === "error") {
302
+ sawError = true;
303
+ yield parsed;
304
+ } else if (parsed.type === "text") {
305
+ yield { ...parsed, project: false, groupKey };
306
+ } else if (parsed.type === "runtime_session") {
307
+ yield parsed;
308
+ } else {
309
+ yield { ...parsed, groupKey };
310
+ }
311
+ }
312
+ }
313
+
194
314
  while (true) {
195
315
  const next = await state.parser.next();
196
316