@code-yeongyu/senpi-agent-core 2026.10.18 → 2026.10.19-2
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.
- package/dist/agent-loop.js +28 -7
- package/dist/agent.d.ts +35 -0
- package/dist/agent.js +88 -28
- package/dist/harness/pico3/bash.js +5 -1
- package/dist/types.d.ts +6 -0
- package/package.json +3 -3
package/dist/agent-loop.js
CHANGED
|
@@ -105,19 +105,29 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
105
105
|
let explicitContinuation = false;
|
|
106
106
|
// Messages from `prepareNextTurn`, appended before the next provider request.
|
|
107
107
|
let preparedMessages = [];
|
|
108
|
+
// Which queue the current pendingMessages were drained from, so consumption is committed against it.
|
|
109
|
+
let pendingSource = "steering";
|
|
110
|
+
const drainSteering = async () => {
|
|
111
|
+
pendingSource = "steering";
|
|
112
|
+
return (await config.getSteeringMessages?.()) || [];
|
|
113
|
+
};
|
|
114
|
+
const drainFollowUp = async () => {
|
|
115
|
+
pendingSource = "followUp";
|
|
116
|
+
return (await config.getFollowUpMessages?.()) || [];
|
|
117
|
+
};
|
|
108
118
|
const refreshTerminatingQueueDrain = async () => {
|
|
109
119
|
if (!drainedTerminatingQueue || !config.restorePendingMessages)
|
|
110
120
|
return;
|
|
111
121
|
await config.restorePendingMessages(drainedTerminatingQueue, pendingMessages);
|
|
112
|
-
pendingMessages =
|
|
122
|
+
pendingMessages = await drainSteering();
|
|
113
123
|
drainedTerminatingQueue = pendingMessages.length > 0 ? "steering" : undefined;
|
|
114
124
|
if (pendingMessages.length === 0) {
|
|
115
|
-
pendingMessages =
|
|
125
|
+
pendingMessages = await drainFollowUp();
|
|
116
126
|
drainedTerminatingQueue = pendingMessages.length > 0 ? "followUp" : undefined;
|
|
117
127
|
}
|
|
118
128
|
};
|
|
119
129
|
// Check for steering messages at start (user may have typed while waiting)
|
|
120
|
-
let pendingMessages =
|
|
130
|
+
let pendingMessages = await drainSteering();
|
|
121
131
|
// Outer loop: continues when queued follow-up messages arrive after agent would stop
|
|
122
132
|
while (true) {
|
|
123
133
|
let hasMoreToolCalls = true;
|
|
@@ -140,6 +150,17 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
140
150
|
}
|
|
141
151
|
drainedTerminatingQueue = undefined;
|
|
142
152
|
}
|
|
153
|
+
// Commit the queued messages' consumption before any of them reaches the context or a provider;
|
|
154
|
+
// a rejected commit restores them and fails this attempt.
|
|
155
|
+
if (pendingMessages.length > 0 && config.commitQueuedConsumption) {
|
|
156
|
+
try {
|
|
157
|
+
await config.commitQueuedConsumption(pendingSource, pendingMessages);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
await config.restorePendingMessages?.(pendingSource, pendingMessages);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
143
164
|
// Process prepared and queued messages before the next assistant response.
|
|
144
165
|
for (const message of declareToolChanges(currentContext, [...preparedMessages, ...pendingMessages], config.model)) {
|
|
145
166
|
await emit({ type: "message_start", message });
|
|
@@ -224,11 +245,11 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
224
245
|
}
|
|
225
246
|
explicitContinuation = decision?.action === "continue";
|
|
226
247
|
if (toolBatchTerminated) {
|
|
227
|
-
pendingMessages =
|
|
248
|
+
pendingMessages = await drainSteering();
|
|
228
249
|
if (pendingMessages.length > 0)
|
|
229
250
|
drainedTerminatingQueue = "steering";
|
|
230
251
|
if (pendingMessages.length === 0) {
|
|
231
|
-
pendingMessages =
|
|
252
|
+
pendingMessages = await drainFollowUp();
|
|
232
253
|
if (pendingMessages.length > 0)
|
|
233
254
|
drainedTerminatingQueue = "followUp";
|
|
234
255
|
}
|
|
@@ -272,14 +293,14 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
272
293
|
drainedTerminatingQueue = undefined;
|
|
273
294
|
}
|
|
274
295
|
if (!toolBatchTerminated) {
|
|
275
|
-
pendingMessages =
|
|
296
|
+
pendingMessages = await drainSteering();
|
|
276
297
|
}
|
|
277
298
|
if (hasMoreToolCalls || pendingMessages.length > 0) {
|
|
278
299
|
explicitContinuation = false;
|
|
279
300
|
}
|
|
280
301
|
}
|
|
281
302
|
// Agent would stop here. Check for follow-up messages.
|
|
282
|
-
const followUpMessages =
|
|
303
|
+
const followUpMessages = await drainFollowUp();
|
|
283
304
|
if (followUpMessages.length > 0) {
|
|
284
305
|
// Set as pending so inner loop processes them
|
|
285
306
|
explicitContinuation = false;
|
package/dist/agent.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface AgentOptions {
|
|
|
24
24
|
prepareNextTurnWithContext?: (context: PrepareNextTurnContext, signal?: AbortSignal) => Promise<AgentLoopTurnUpdate | undefined> | AgentLoopTurnUpdate | undefined;
|
|
25
25
|
steeringMode?: QueueMode;
|
|
26
26
|
followUpMode?: QueueMode;
|
|
27
|
+
/** Lets the owner of queued input commit its consumption before the loop sends it (senpi#3549). */
|
|
28
|
+
queuedInputPort?: QueuedInputPort;
|
|
27
29
|
sessionId?: string;
|
|
28
30
|
thinkingBudgets?: ThinkingBudgets;
|
|
29
31
|
transport?: Transport;
|
|
@@ -37,6 +39,26 @@ export interface AgentOptions {
|
|
|
37
39
|
/** Cursor exec-channel tool handlers; see {@link AgentLoopConfig.cursorExecHandlers}. */
|
|
38
40
|
cursorExecHandlers?: AgentLoopConfig["cursorExecHandlers"];
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Claimed messages going back to their queue. Both answers are false once a clear landed after the claim:
|
|
44
|
+
* `restorable` asks before anything is recorded, `restore` puts the messages back only while that still holds.
|
|
45
|
+
*/
|
|
46
|
+
export interface QueuedGiveBack {
|
|
47
|
+
restorable(): boolean;
|
|
48
|
+
restore(): boolean;
|
|
49
|
+
}
|
|
50
|
+
/** The owner side of the steering and follow-up queues; every drain, restore and consumption goes through it. */
|
|
51
|
+
export interface QueuedInputPort {
|
|
52
|
+
/** Runs `drain` (the native queue's drain) under the owner's gate and returns what it may hand to a turn. */
|
|
53
|
+
claim(queue: "steering" | "followUp", drain: () => AgentMessage[]): Promise<AgentMessage[]>;
|
|
54
|
+
/** Gives claimed messages back under the owner's gate; a clear that landed after the claim wins. */
|
|
55
|
+
release(queue: "steering" | "followUp", messages: AgentMessage[], giveBack: QueuedGiveBack): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Called with drained queued messages before any of them enters the context or a provider request.
|
|
58
|
+
* Resolve once their consumption is durable; a rejection restores them and fails this attempt.
|
|
59
|
+
*/
|
|
60
|
+
commitConsumption(queue: "steering" | "followUp", messages: AgentMessage[]): Promise<void>;
|
|
61
|
+
}
|
|
40
62
|
export interface AgentContinuationOptions {
|
|
41
63
|
/** Keep queued steering and follow-up input out of the continuation's first provider request only. */
|
|
42
64
|
deferQueuedMessages?: boolean;
|
|
@@ -66,6 +88,7 @@ export declare class Agent {
|
|
|
66
88
|
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
|
|
67
89
|
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
|
|
68
90
|
finishTurn?: FinishTurn;
|
|
91
|
+
queuedInputPort?: QueuedInputPort;
|
|
69
92
|
prepareRequest?: PrepareRequest;
|
|
70
93
|
prepareNextTurn?: (signal?: AbortSignal) => Promise<AgentLoopTurnUpdate | undefined> | AgentLoopTurnUpdate | undefined;
|
|
71
94
|
prepareNextTurnWithContext?: (context: PrepareNextTurnContext, signal?: AbortSignal) => Promise<AgentLoopTurnUpdate | undefined> | AgentLoopTurnUpdate | undefined;
|
|
@@ -173,6 +196,18 @@ export declare class Agent {
|
|
|
173
196
|
continue(options?: AgentContinuationOptions): Promise<void>;
|
|
174
197
|
private normalizePromptInput;
|
|
175
198
|
private runPromptMessages;
|
|
199
|
+
/**
|
|
200
|
+
* Starts a run from one queue. Without a port the queue is drained now, as before; with one, the drain is
|
|
201
|
+
* claimed and its consumption committed inside the run, before any of it reaches the context.
|
|
202
|
+
*/
|
|
203
|
+
private runQueuedMessages;
|
|
204
|
+
/**
|
|
205
|
+
* Takes one queue's messages for a run: a plain drain without a port; with one, a claim whose consumption is
|
|
206
|
+
* committed before the caller puts anything into the context, released again if that commit fails.
|
|
207
|
+
*/
|
|
208
|
+
private takeQueuedMessages;
|
|
209
|
+
/** The one give-back every queue exit uses: a clear since `generation` (read at the claim) wins. */
|
|
210
|
+
private giveBack;
|
|
176
211
|
private runContinuation;
|
|
177
212
|
private createContextSnapshot;
|
|
178
213
|
private createLoopConfig;
|
package/dist/agent.js
CHANGED
|
@@ -148,6 +148,7 @@ export class Agent {
|
|
|
148
148
|
this.beforeToolCall = runtimeOptions.beforeToolCall;
|
|
149
149
|
this.afterToolCall = runtimeOptions.afterToolCall;
|
|
150
150
|
this.finishTurn = runtimeOptions.finishTurn;
|
|
151
|
+
this.queuedInputPort = runtimeOptions.queuedInputPort;
|
|
151
152
|
this.prepareRequest = runtimeOptions.prepareRequest;
|
|
152
153
|
this.prepareNextTurn = runtimeOptions.prepareNextTurn;
|
|
153
154
|
this.prepareNextTurnWithContext = runtimeOptions.prepareNextTurnWithContext;
|
|
@@ -303,18 +304,16 @@ export class Agent {
|
|
|
303
304
|
await this.continue(options);
|
|
304
305
|
return;
|
|
305
306
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
await this.runPromptMessages(queuedSteering, {
|
|
307
|
+
if (this.steeringQueue.hasItems()) {
|
|
308
|
+
await this.runQueuedMessages("steering", {
|
|
309
309
|
skipInitialSteeringPoll: true,
|
|
310
310
|
initialRequestTimeoutMs: options.timeoutMs,
|
|
311
311
|
initialRequestStreamStartTimeoutMs: options.streamStartTimeoutMs,
|
|
312
312
|
});
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
await this.runPromptMessages(queuedFollowUps, {
|
|
315
|
+
if (this.followUpQueue.hasItems()) {
|
|
316
|
+
await this.runQueuedMessages("followUp", {
|
|
318
317
|
initialRequestTimeoutMs: options.timeoutMs,
|
|
319
318
|
initialRequestStreamStartTimeoutMs: options.streamStartTimeoutMs,
|
|
320
319
|
});
|
|
@@ -336,18 +335,16 @@ export class Agent {
|
|
|
336
335
|
throw new Error("No messages to continue from");
|
|
337
336
|
}
|
|
338
337
|
if (lastMessage.role === "assistant") {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
await this.runPromptMessages(queuedSteering, {
|
|
338
|
+
if (this.steeringQueue.hasItems()) {
|
|
339
|
+
await this.runQueuedMessages("steering", {
|
|
342
340
|
skipInitialSteeringPoll: true,
|
|
343
341
|
initialRequestTimeoutMs: options.timeoutMs,
|
|
344
342
|
initialRequestStreamStartTimeoutMs: options.streamStartTimeoutMs,
|
|
345
343
|
});
|
|
346
344
|
return;
|
|
347
345
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
await this.runPromptMessages(queuedFollowUps, {
|
|
346
|
+
if (this.followUpQueue.hasItems()) {
|
|
347
|
+
await this.runQueuedMessages("followUp", {
|
|
351
348
|
initialRequestTimeoutMs: options.timeoutMs,
|
|
352
349
|
initialRequestStreamStartTimeoutMs: options.streamStartTimeoutMs,
|
|
353
350
|
});
|
|
@@ -375,6 +372,63 @@ export class Agent {
|
|
|
375
372
|
await runAgentLoop(messages, this.createContextSnapshot(), this.createLoopConfig(options), (event) => this.processEvents(event), signal, this.streamFunction);
|
|
376
373
|
});
|
|
377
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Starts a run from one queue. Without a port the queue is drained now, as before; with one, the drain is
|
|
377
|
+
* claimed and its consumption committed inside the run, before any of it reaches the context.
|
|
378
|
+
*/
|
|
379
|
+
async runQueuedMessages(queue, options) {
|
|
380
|
+
const pendingQueue = queue === "steering" ? this.steeringQueue : this.followUpQueue;
|
|
381
|
+
const port = this.queuedInputPort;
|
|
382
|
+
if (!port) {
|
|
383
|
+
await this.runPromptMessages(pendingQueue.drain(), options);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
await this.runWithLifecycle(async (signal) => {
|
|
387
|
+
const messages = await this.takeQueuedMessages(queue);
|
|
388
|
+
if (messages.length === 0)
|
|
389
|
+
return;
|
|
390
|
+
await runAgentLoop(messages, this.createContextSnapshot(), this.createLoopConfig(options), (event) => this.processEvents(event), signal, this.streamFunction);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Takes one queue's messages for a run: a plain drain without a port; with one, a claim whose consumption is
|
|
395
|
+
* committed before the caller puts anything into the context, released again if that commit fails.
|
|
396
|
+
*/
|
|
397
|
+
async takeQueuedMessages(queue) {
|
|
398
|
+
const pendingQueue = queue === "steering" ? this.steeringQueue : this.followUpQueue;
|
|
399
|
+
const port = this.queuedInputPort;
|
|
400
|
+
if (!port)
|
|
401
|
+
return pendingQueue.drain();
|
|
402
|
+
let generation = pendingQueue.getClearGeneration();
|
|
403
|
+
const messages = await port.claim(queue, () => {
|
|
404
|
+
generation = pendingQueue.getClearGeneration();
|
|
405
|
+
return pendingQueue.drain();
|
|
406
|
+
});
|
|
407
|
+
if (messages.length === 0)
|
|
408
|
+
return messages;
|
|
409
|
+
try {
|
|
410
|
+
await port.commitConsumption(queue, messages);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
await port.release(queue, messages, this.giveBack(queue, messages, generation));
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
return messages;
|
|
417
|
+
}
|
|
418
|
+
/** The one give-back every queue exit uses: a clear since `generation` (read at the claim) wins. */
|
|
419
|
+
giveBack(queue, messages, generation) {
|
|
420
|
+
const pendingQueue = queue === "steering" ? this.steeringQueue : this.followUpQueue;
|
|
421
|
+
const restorable = () => pendingQueue.getClearGeneration() === generation;
|
|
422
|
+
return {
|
|
423
|
+
restorable,
|
|
424
|
+
restore: () => {
|
|
425
|
+
if (!restorable())
|
|
426
|
+
return false;
|
|
427
|
+
pendingQueue.prepend(messages);
|
|
428
|
+
return true;
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
378
432
|
async runContinuation(options = {}) {
|
|
379
433
|
await this.runWithLifecycle(async (signal) => {
|
|
380
434
|
await runAgentLoopContinue(this.createContextSnapshot(), this.createLoopConfig({
|
|
@@ -397,6 +451,8 @@ export class Agent {
|
|
|
397
451
|
let steeringQueueGeneration = this.steeringQueue.getClearGeneration();
|
|
398
452
|
let followUpQueueGeneration = this.followUpQueue.getClearGeneration();
|
|
399
453
|
const finishTurn = this.finishTurn;
|
|
454
|
+
const queuedInputPort = this.queuedInputPort;
|
|
455
|
+
const giveBack = (queue, messages) => this.giveBack(queue, messages, queue === "steering" ? steeringQueueGeneration : followUpQueueGeneration);
|
|
400
456
|
const reasoning = this._state.reasoningBaseline ?? this._state.thinkingLevel;
|
|
401
457
|
return {
|
|
402
458
|
model: this._state.model,
|
|
@@ -447,23 +503,27 @@ export class Agent {
|
|
|
447
503
|
skipInitialSteeringPoll = false;
|
|
448
504
|
return [];
|
|
449
505
|
}
|
|
450
|
-
|
|
451
|
-
|
|
506
|
+
const drain = () => {
|
|
507
|
+
steeringQueueGeneration = this.steeringQueue.getClearGeneration();
|
|
508
|
+
return this.steeringQueue.drain();
|
|
509
|
+
};
|
|
510
|
+
return queuedInputPort ? queuedInputPort.claim("steering", drain) : drain();
|
|
452
511
|
},
|
|
453
512
|
getFollowUpMessages: async () => {
|
|
454
|
-
|
|
455
|
-
|
|
513
|
+
const drain = () => {
|
|
514
|
+
followUpQueueGeneration = this.followUpQueue.getClearGeneration();
|
|
515
|
+
return this.followUpQueue.drain();
|
|
516
|
+
};
|
|
517
|
+
return queuedInputPort ? queuedInputPort.claim("followUp", drain) : drain();
|
|
456
518
|
},
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return;
|
|
466
|
-
this.followUpQueue.prepend(messages);
|
|
519
|
+
commitQueuedConsumption: queuedInputPort
|
|
520
|
+
? (queue, messages) => queuedInputPort.commitConsumption(queue, messages)
|
|
521
|
+
: undefined,
|
|
522
|
+
restorePendingMessages: async (queue, messages) => {
|
|
523
|
+
if (queuedInputPort)
|
|
524
|
+
await queuedInputPort.release(queue, messages, giveBack(queue, messages));
|
|
525
|
+
else
|
|
526
|
+
giveBack(queue, messages).restore();
|
|
467
527
|
},
|
|
468
528
|
};
|
|
469
529
|
}
|
|
@@ -506,12 +566,12 @@ export class Agent {
|
|
|
506
566
|
(lastMessage.stopReason !== "error" && lastMessage.stopReason !== "aborted"));
|
|
507
567
|
}
|
|
508
568
|
async runQueuedMessagesAfterAgentEnd(signal) {
|
|
509
|
-
const queuedSteering = this.steeringQueue.
|
|
569
|
+
const queuedSteering = this.steeringQueue.hasItems() ? await this.takeQueuedMessages("steering") : [];
|
|
510
570
|
if (queuedSteering.length > 0) {
|
|
511
571
|
await runAgentLoop(queuedSteering, this.createContextSnapshot(), this.createLoopConfig({ skipInitialSteeringPoll: true }), (event) => this.processEvents(event), signal, this.streamFunction);
|
|
512
572
|
return;
|
|
513
573
|
}
|
|
514
|
-
const queuedFollowUps = this.followUpQueue.
|
|
574
|
+
const queuedFollowUps = this.followUpQueue.hasItems() ? await this.takeQueuedMessages("followUp") : [];
|
|
515
575
|
if (queuedFollowUps.length === 0) {
|
|
516
576
|
return;
|
|
517
577
|
}
|
|
@@ -12,7 +12,11 @@ export function bashTool(output = {}) {
|
|
|
12
12
|
async execute({ command, cwd }, api, ctx) {
|
|
13
13
|
const started = Date.now();
|
|
14
14
|
const child = spawn("bash", ["-c", command], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
15
|
-
|
|
15
|
+
// A child that never started has no pid; on Node < 24.22 / 26.10 its kill() signals our own process group.
|
|
16
|
+
const onAbort = () => {
|
|
17
|
+
if (child.pid !== undefined)
|
|
18
|
+
child.kill("SIGKILL");
|
|
19
|
+
};
|
|
16
20
|
ctx.abortSignal?.addEventListener("abort", onAbort);
|
|
17
21
|
child.stdout.on("data", (c) => api.stream(c));
|
|
18
22
|
child.stderr.on("data", (c) => api.stream(c));
|
package/dist/types.d.ts
CHANGED
|
@@ -299,6 +299,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
299
299
|
* Restored messages must be placed before messages queued after the drain.
|
|
300
300
|
*/
|
|
301
301
|
restorePendingMessages?: (queue: "steering" | "followUp", messages: AgentMessage[]) => Promise<void> | void;
|
|
302
|
+
/**
|
|
303
|
+
* Called with the queued messages the loop is about to put into the context, after they were drained and
|
|
304
|
+
* before any of them is inserted or sent. A rejection keeps them out of this turn (they are restored), so a
|
|
305
|
+
* message is never seen by a provider without its consumption committed first (senpi#3549).
|
|
306
|
+
*/
|
|
307
|
+
commitQueuedConsumption?: (queue: "steering" | "followUp", messages: AgentMessage[]) => Promise<void>;
|
|
302
308
|
/**
|
|
303
309
|
* Tool execution mode.
|
|
304
310
|
* - "sequential": execute tool calls one by one
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-agent-core",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.19-2",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@earendil-works/chord": "0.99.1",
|
|
65
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
66
|
-
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.
|
|
65
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.19-2",
|
|
66
|
+
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.19-2",
|
|
67
67
|
"diff": "9.0.0",
|
|
68
68
|
"ignore": "7.0.9",
|
|
69
69
|
"typebox": "1.3.34",
|