@t2000/engine 0.46.7 → 0.46.8
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/index.d.ts +96 -1
- package/dist/index.js +174 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -888,6 +888,8 @@ declare class QueryEngine {
|
|
|
888
888
|
private messages;
|
|
889
889
|
private abortController;
|
|
890
890
|
private guardEvents;
|
|
891
|
+
private readonly turnReadCache;
|
|
892
|
+
private turnPaused;
|
|
891
893
|
constructor(config: EngineConfig);
|
|
892
894
|
/**
|
|
893
895
|
* Submit a user message and stream engine events.
|
|
@@ -1241,6 +1243,89 @@ interface MicrocompactResult extends Array<Message> {
|
|
|
1241
1243
|
*/
|
|
1242
1244
|
declare function microcompact(messages: readonly Message[], tools?: readonly Tool[]): MicrocompactResult;
|
|
1243
1245
|
|
|
1246
|
+
/**
|
|
1247
|
+
* [v0.46.8] Intra-turn deduplication of read-only tool calls.
|
|
1248
|
+
*
|
|
1249
|
+
* # Problem
|
|
1250
|
+
* Two independent execution paths can call the same read-only tool within
|
|
1251
|
+
* the same user turn:
|
|
1252
|
+
* 1. Host pre-dispatch via `engine.invokeReadTool()` (deterministic — runs
|
|
1253
|
+
* before the LLM ever sees the message; injects a synthetic
|
|
1254
|
+
* `tool_use`+`tool_result` pair into the ledger so the card renders
|
|
1255
|
+
* immediately and the LLM has the data).
|
|
1256
|
+
* 2. The LLM itself, mid-turn, emitting a `tool_use` block for the same
|
|
1257
|
+
* tool (often because the prompt says "always call balance_check on
|
|
1258
|
+
* direct read questions" and the model doesn't trust the synthetic
|
|
1259
|
+
* pair).
|
|
1260
|
+
*
|
|
1261
|
+
* Both paths emit a `tool_result` SSE event, the host renders BOTH cards,
|
|
1262
|
+
* the user sees a duplicate. Coordinating these two paths via prompt rules
|
|
1263
|
+
* is probabilistic ("DO NOT re-call when you see a synthetic pair") and
|
|
1264
|
+
* has empirically shown ~30% miss rate — the LLM still re-calls anyway.
|
|
1265
|
+
*
|
|
1266
|
+
* # Fix
|
|
1267
|
+
* Idempotent intra-turn cache. Within one user turn:
|
|
1268
|
+
* - Calling the same read-only tool with the same args twice returns the
|
|
1269
|
+
* cached result on the second call.
|
|
1270
|
+
* - The second call yields a `tool_result` event with `resultDeduped:true`
|
|
1271
|
+
* so hosts can skip rendering a duplicate card while the LLM still gets
|
|
1272
|
+
* the data it needs to satisfy its `tool_use` id.
|
|
1273
|
+
*
|
|
1274
|
+
* # Lifecycle
|
|
1275
|
+
* - Cache lives on the `QueryEngine` instance.
|
|
1276
|
+
* - Populated by `invokeReadTool` (host pre-dispatch) AND by the agent
|
|
1277
|
+
* loop's tool-execution path (LLM-driven calls).
|
|
1278
|
+
* - Cleared on `turn_complete` (clean slate for the next user turn).
|
|
1279
|
+
* - Cleared whenever a WRITE tool executes successfully (writes mutate
|
|
1280
|
+
* on-chain state, so any subsequent read in the same turn must re-fetch
|
|
1281
|
+
* for freshness).
|
|
1282
|
+
* - Cleared on errors / abort (defensive cleanup).
|
|
1283
|
+
*
|
|
1284
|
+
* # Why not just extend microcompact?
|
|
1285
|
+
* `microcompact` does CROSS-turn dedup, but explicitly excludes
|
|
1286
|
+
* `cacheable: false` tools (balance_check, health_check, savings_info,
|
|
1287
|
+
* transaction_history) so post-write refreshes always surface fresh data.
|
|
1288
|
+
* Within a single turn (pre-write), those same tools are perfectly
|
|
1289
|
+
* dedup-able — state can't change. This cache fills that exact gap.
|
|
1290
|
+
*
|
|
1291
|
+
* # Invariants
|
|
1292
|
+
* - Read-only tools only. Write tools never enter the cache.
|
|
1293
|
+
* - Errored results are NEVER cached (the next call should retry).
|
|
1294
|
+
* - Cache key includes the full input, stably stringified — different
|
|
1295
|
+
* filter args (e.g. `transaction_history({minUsd:5})` vs
|
|
1296
|
+
* `transaction_history({})`) hit different cache entries.
|
|
1297
|
+
*/
|
|
1298
|
+
declare class TurnReadCache {
|
|
1299
|
+
private readonly store;
|
|
1300
|
+
/**
|
|
1301
|
+
* Build the cache key for a (toolName, input) pair. Stable across object
|
|
1302
|
+
* key ordering so `{a:1,b:2}` and `{b:2,a:1}` map to the same entry.
|
|
1303
|
+
*/
|
|
1304
|
+
static keyFor(toolName: string, input: unknown): string;
|
|
1305
|
+
has(key: string): boolean;
|
|
1306
|
+
get(key: string): {
|
|
1307
|
+
result: unknown;
|
|
1308
|
+
sourceToolUseId: string;
|
|
1309
|
+
} | undefined;
|
|
1310
|
+
/**
|
|
1311
|
+
* Populate the cache. Caller is responsible for ensuring the result was
|
|
1312
|
+
* a successful read (no errors). Overwrites any prior entry for the same
|
|
1313
|
+
* key — the most recent successful read wins, which is correct under our
|
|
1314
|
+
* "writes invalidate the whole cache" invariant.
|
|
1315
|
+
*/
|
|
1316
|
+
set(key: string, value: {
|
|
1317
|
+
result: unknown;
|
|
1318
|
+
sourceToolUseId: string;
|
|
1319
|
+
}): void;
|
|
1320
|
+
/**
|
|
1321
|
+
* Drop every entry. Called at turn end and after every successful write.
|
|
1322
|
+
* Cheap and intentional — the cache is small (a handful of entries per
|
|
1323
|
+
* turn at most) and clearing is the correct response to any state mutation.
|
|
1324
|
+
*/
|
|
1325
|
+
clear(): void;
|
|
1326
|
+
size(): number;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1244
1329
|
/**
|
|
1245
1330
|
* EarlyToolDispatcher — dispatches read-only tools mid-stream.
|
|
1246
1331
|
*
|
|
@@ -1257,11 +1342,21 @@ declare class EarlyToolDispatcher {
|
|
|
1257
1342
|
private entries;
|
|
1258
1343
|
private readonly tools;
|
|
1259
1344
|
private readonly context;
|
|
1345
|
+
private readonly turnReadCache;
|
|
1260
1346
|
private abortController;
|
|
1261
|
-
constructor(tools: Tool[], context: ToolContext);
|
|
1347
|
+
constructor(tools: Tool[], context: ToolContext, turnReadCache?: TurnReadCache);
|
|
1262
1348
|
/**
|
|
1263
1349
|
* Attempt to dispatch a tool call. Returns true if the tool was dispatched
|
|
1264
1350
|
* (read-only + concurrency-safe), false if it should be queued for later.
|
|
1351
|
+
*
|
|
1352
|
+
* [v0.46.8] Cache-aware: if a `TurnReadCache` was supplied at
|
|
1353
|
+
* construction and a prior call this turn already produced a result
|
|
1354
|
+
* for the same `(toolName, input)`, the dispatcher returns true (the
|
|
1355
|
+
* call IS handled here, not queued for the post-stream loop) but
|
|
1356
|
+
* skips the tool execution entirely — `collectResults` will surface
|
|
1357
|
+
* the cached value with `resultDeduped: true`. On a cache miss for
|
|
1358
|
+
* a successful real execution, the result is written back to the
|
|
1359
|
+
* cache so any later call within the same turn dedups too.
|
|
1265
1360
|
*/
|
|
1266
1361
|
tryDispatch(call: PendingToolCall): boolean;
|
|
1267
1362
|
/** True if any tools have been dispatched. */
|
package/dist/index.js
CHANGED
|
@@ -4257,27 +4257,116 @@ function safeNum(v) {
|
|
|
4257
4257
|
return isNaN(n) ? 0 : n;
|
|
4258
4258
|
}
|
|
4259
4259
|
|
|
4260
|
+
// src/turn-read-cache.ts
|
|
4261
|
+
var TurnReadCache = class {
|
|
4262
|
+
store = /* @__PURE__ */ new Map();
|
|
4263
|
+
/**
|
|
4264
|
+
* Build the cache key for a (toolName, input) pair. Stable across object
|
|
4265
|
+
* key ordering so `{a:1,b:2}` and `{b:2,a:1}` map to the same entry.
|
|
4266
|
+
*/
|
|
4267
|
+
static keyFor(toolName, input) {
|
|
4268
|
+
return `${toolName}:${stableStringify2(input)}`;
|
|
4269
|
+
}
|
|
4270
|
+
has(key) {
|
|
4271
|
+
return this.store.has(key);
|
|
4272
|
+
}
|
|
4273
|
+
get(key) {
|
|
4274
|
+
return this.store.get(key);
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Populate the cache. Caller is responsible for ensuring the result was
|
|
4278
|
+
* a successful read (no errors). Overwrites any prior entry for the same
|
|
4279
|
+
* key — the most recent successful read wins, which is correct under our
|
|
4280
|
+
* "writes invalidate the whole cache" invariant.
|
|
4281
|
+
*/
|
|
4282
|
+
set(key, value) {
|
|
4283
|
+
this.store.set(key, value);
|
|
4284
|
+
}
|
|
4285
|
+
/**
|
|
4286
|
+
* Drop every entry. Called at turn end and after every successful write.
|
|
4287
|
+
* Cheap and intentional — the cache is small (a handful of entries per
|
|
4288
|
+
* turn at most) and clearing is the correct response to any state mutation.
|
|
4289
|
+
*/
|
|
4290
|
+
clear() {
|
|
4291
|
+
this.store.clear();
|
|
4292
|
+
}
|
|
4293
|
+
size() {
|
|
4294
|
+
return this.store.size;
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
function stableStringify2(value) {
|
|
4298
|
+
if (value === null || value === void 0) return "";
|
|
4299
|
+
if (typeof value !== "object") return JSON.stringify(value);
|
|
4300
|
+
if (Array.isArray(value)) return JSON.stringify(value.map(stableStringifyForObject));
|
|
4301
|
+
return stableStringifyForObject(value);
|
|
4302
|
+
}
|
|
4303
|
+
function stableStringifyForObject(value) {
|
|
4304
|
+
if (value === null || value === void 0) return JSON.stringify(value);
|
|
4305
|
+
if (typeof value !== "object") return JSON.stringify(value);
|
|
4306
|
+
if (Array.isArray(value)) {
|
|
4307
|
+
return `[${value.map(stableStringifyForObject).join(",")}]`;
|
|
4308
|
+
}
|
|
4309
|
+
const sorted = Object.keys(value).sort();
|
|
4310
|
+
const parts = sorted.map(
|
|
4311
|
+
(k) => `${JSON.stringify(k)}:${stableStringifyForObject(value[k])}`
|
|
4312
|
+
);
|
|
4313
|
+
return `{${parts.join(",")}}`;
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4260
4316
|
// src/early-dispatcher.ts
|
|
4261
4317
|
var EarlyToolDispatcher = class {
|
|
4262
4318
|
entries = [];
|
|
4263
4319
|
tools;
|
|
4264
4320
|
context;
|
|
4321
|
+
turnReadCache;
|
|
4265
4322
|
abortController;
|
|
4266
|
-
constructor(tools, context) {
|
|
4323
|
+
constructor(tools, context, turnReadCache) {
|
|
4267
4324
|
this.tools = tools;
|
|
4268
4325
|
this.context = context;
|
|
4326
|
+
this.turnReadCache = turnReadCache;
|
|
4269
4327
|
this.abortController = new AbortController();
|
|
4270
4328
|
}
|
|
4271
4329
|
/**
|
|
4272
4330
|
* Attempt to dispatch a tool call. Returns true if the tool was dispatched
|
|
4273
4331
|
* (read-only + concurrency-safe), false if it should be queued for later.
|
|
4332
|
+
*
|
|
4333
|
+
* [v0.46.8] Cache-aware: if a `TurnReadCache` was supplied at
|
|
4334
|
+
* construction and a prior call this turn already produced a result
|
|
4335
|
+
* for the same `(toolName, input)`, the dispatcher returns true (the
|
|
4336
|
+
* call IS handled here, not queued for the post-stream loop) but
|
|
4337
|
+
* skips the tool execution entirely — `collectResults` will surface
|
|
4338
|
+
* the cached value with `resultDeduped: true`. On a cache miss for
|
|
4339
|
+
* a successful real execution, the result is written back to the
|
|
4340
|
+
* cache so any later call within the same turn dedups too.
|
|
4274
4341
|
*/
|
|
4275
4342
|
tryDispatch(call) {
|
|
4276
4343
|
const tool = findTool(this.tools, call.name);
|
|
4277
4344
|
if (!tool || !tool.isReadOnly || !tool.isConcurrencySafe) return false;
|
|
4345
|
+
if (this.turnReadCache) {
|
|
4346
|
+
const cacheKey = TurnReadCache.keyFor(call.name, call.input);
|
|
4347
|
+
const cached = this.turnReadCache.get(cacheKey);
|
|
4348
|
+
if (cached) {
|
|
4349
|
+
this.entries.push({
|
|
4350
|
+
call,
|
|
4351
|
+
tool,
|
|
4352
|
+
promise: Promise.resolve({ data: cached.result, isError: false }),
|
|
4353
|
+
deduped: true
|
|
4354
|
+
});
|
|
4355
|
+
return true;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4278
4358
|
const childContext = { ...this.context, signal: this.abortController.signal };
|
|
4279
|
-
const promise = executeTool(tool, call, childContext)
|
|
4280
|
-
|
|
4359
|
+
const promise = executeTool(tool, call, childContext).then((result) => {
|
|
4360
|
+
if (!result.isError && this.turnReadCache) {
|
|
4361
|
+
const cacheKey = TurnReadCache.keyFor(call.name, call.input);
|
|
4362
|
+
this.turnReadCache.set(cacheKey, {
|
|
4363
|
+
result: result.data,
|
|
4364
|
+
sourceToolUseId: call.id
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
return result;
|
|
4368
|
+
});
|
|
4369
|
+
this.entries.push({ call, tool, promise, deduped: false });
|
|
4281
4370
|
return true;
|
|
4282
4371
|
}
|
|
4283
4372
|
/** True if any tools have been dispatched. */
|
|
@@ -4303,7 +4392,8 @@ var EarlyToolDispatcher = class {
|
|
|
4303
4392
|
toolUseId: entry.call.id,
|
|
4304
4393
|
result: budgeted,
|
|
4305
4394
|
isError: result.isError,
|
|
4306
|
-
wasEarlyDispatched: true
|
|
4395
|
+
wasEarlyDispatched: true,
|
|
4396
|
+
...entry.deduped ? { resultDeduped: true } : {}
|
|
4307
4397
|
};
|
|
4308
4398
|
} catch (err) {
|
|
4309
4399
|
yield {
|
|
@@ -4375,6 +4465,18 @@ var QueryEngine = class {
|
|
|
4375
4465
|
messages = [];
|
|
4376
4466
|
abortController = null;
|
|
4377
4467
|
guardEvents = [];
|
|
4468
|
+
// [v0.46.8] Intra-turn dedup cache for read-only tool calls. See
|
|
4469
|
+
// `turn-read-cache.ts` for the full lifecycle. Key takeaway: the cache
|
|
4470
|
+
// lives across the host's pre-dispatch (`invokeReadTool`) and the
|
|
4471
|
+
// agent loop's LLM-driven tool execution within ONE user turn, then
|
|
4472
|
+
// clears on `turn_complete` or after any successful write.
|
|
4473
|
+
turnReadCache = new TurnReadCache();
|
|
4474
|
+
// [v0.46.8] Set to `true` when the agent loop yields `pending_action`
|
|
4475
|
+
// and returns (turn is paused awaiting user confirmation). The
|
|
4476
|
+
// submitMessage / resumeWithToolResult wrappers consult this flag in
|
|
4477
|
+
// their `finally` block so they DON'T clear the cache mid-turn — the
|
|
4478
|
+
// pending write may resume, and the cache should survive the pause.
|
|
4479
|
+
turnPaused = false;
|
|
4378
4480
|
constructor(config) {
|
|
4379
4481
|
this.provider = config.provider;
|
|
4380
4482
|
this.agent = config.agent;
|
|
@@ -4426,7 +4528,14 @@ var QueryEngine = class {
|
|
|
4426
4528
|
role: "user",
|
|
4427
4529
|
content: [{ type: "text", text: prompt }]
|
|
4428
4530
|
});
|
|
4429
|
-
|
|
4531
|
+
this.turnPaused = false;
|
|
4532
|
+
try {
|
|
4533
|
+
yield* this.agentLoop(prompt, signal);
|
|
4534
|
+
} finally {
|
|
4535
|
+
if (!this.turnPaused) {
|
|
4536
|
+
this.turnReadCache.clear();
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4430
4539
|
}
|
|
4431
4540
|
/**
|
|
4432
4541
|
* Resume the conversation after a pending action is resolved.
|
|
@@ -4470,10 +4579,19 @@ var QueryEngine = class {
|
|
|
4470
4579
|
};
|
|
4471
4580
|
if (!response.approved) {
|
|
4472
4581
|
yield { type: "turn_complete", stopReason: "end_turn" };
|
|
4582
|
+
this.turnReadCache.clear();
|
|
4473
4583
|
return;
|
|
4474
4584
|
}
|
|
4585
|
+
this.turnReadCache.clear();
|
|
4475
4586
|
yield* this.runPostWriteRefresh(action, response, signal);
|
|
4476
|
-
|
|
4587
|
+
this.turnPaused = false;
|
|
4588
|
+
try {
|
|
4589
|
+
yield* this.agentLoop(null, signal, false);
|
|
4590
|
+
} finally {
|
|
4591
|
+
if (!this.turnPaused) {
|
|
4592
|
+
this.turnReadCache.clear();
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4477
4595
|
}
|
|
4478
4596
|
/**
|
|
4479
4597
|
* [v1.5] Auto-run configured read tools after a successful write,
|
|
@@ -4552,6 +4670,12 @@ var QueryEngine = class {
|
|
|
4552
4670
|
}));
|
|
4553
4671
|
this.messages.push({ role: "user", content: refreshResults });
|
|
4554
4672
|
for (const r of refreshes) {
|
|
4673
|
+
if (!r.isError) {
|
|
4674
|
+
this.turnReadCache.set(
|
|
4675
|
+
TurnReadCache.keyFor(r.tool.name, {}),
|
|
4676
|
+
{ result: r.data, sourceToolUseId: r.id }
|
|
4677
|
+
);
|
|
4678
|
+
}
|
|
4555
4679
|
yield {
|
|
4556
4680
|
type: "tool_result",
|
|
4557
4681
|
toolName: r.tool.name,
|
|
@@ -4619,6 +4743,11 @@ var QueryEngine = class {
|
|
|
4619
4743
|
`invokeReadTool: invalid input for ${toolName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
|
4620
4744
|
);
|
|
4621
4745
|
}
|
|
4746
|
+
const cacheKey = TurnReadCache.keyFor(toolName, parsed.data);
|
|
4747
|
+
const cached = this.turnReadCache.get(cacheKey);
|
|
4748
|
+
if (cached) {
|
|
4749
|
+
return { data: cached.result, isError: false };
|
|
4750
|
+
}
|
|
4622
4751
|
const signal = options.signal ?? new AbortController().signal;
|
|
4623
4752
|
const context = {
|
|
4624
4753
|
agent: this.agent,
|
|
@@ -4635,6 +4764,10 @@ var QueryEngine = class {
|
|
|
4635
4764
|
};
|
|
4636
4765
|
try {
|
|
4637
4766
|
const result = await tool.call(parsed.data, context);
|
|
4767
|
+
this.turnReadCache.set(cacheKey, {
|
|
4768
|
+
result: result.data,
|
|
4769
|
+
sourceToolUseId: "invokeReadTool"
|
|
4770
|
+
});
|
|
4638
4771
|
return { data: result.data, isError: false };
|
|
4639
4772
|
} catch (err) {
|
|
4640
4773
|
return {
|
|
@@ -4687,7 +4820,7 @@ var QueryEngine = class {
|
|
|
4687
4820
|
assistantBlocks: [],
|
|
4688
4821
|
pendingToolCalls: []
|
|
4689
4822
|
};
|
|
4690
|
-
const dispatcher = new EarlyToolDispatcher(this.tools, context);
|
|
4823
|
+
const dispatcher = new EarlyToolDispatcher(this.tools, context, this.turnReadCache);
|
|
4691
4824
|
try {
|
|
4692
4825
|
const microcompacted = microcompact(this.messages, this.tools);
|
|
4693
4826
|
this.messages = microcompacted;
|
|
@@ -4859,6 +4992,27 @@ ${recipeCtx}`;
|
|
|
4859
4992
|
let pendingWrite = null;
|
|
4860
4993
|
for (const call of acc.pendingToolCalls) {
|
|
4861
4994
|
const tool = findTool(this.tools, call.name);
|
|
4995
|
+
if (tool && tool.isReadOnly) {
|
|
4996
|
+
const cacheKey = TurnReadCache.keyFor(call.name, call.input);
|
|
4997
|
+
const cached = this.turnReadCache.get(cacheKey);
|
|
4998
|
+
if (cached) {
|
|
4999
|
+
yield {
|
|
5000
|
+
type: "tool_result",
|
|
5001
|
+
toolName: call.name,
|
|
5002
|
+
toolUseId: call.id,
|
|
5003
|
+
result: cached.result,
|
|
5004
|
+
isError: false,
|
|
5005
|
+
resultDeduped: true
|
|
5006
|
+
};
|
|
5007
|
+
toolResultBlocks.push({
|
|
5008
|
+
type: "tool_result",
|
|
5009
|
+
toolUseId: call.id,
|
|
5010
|
+
content: JSON.stringify(cached.result),
|
|
5011
|
+
isError: false
|
|
5012
|
+
});
|
|
5013
|
+
continue;
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
4862
5016
|
const needsConfirmation = (() => {
|
|
4863
5017
|
if (!tool || tool.isReadOnly) return false;
|
|
4864
5018
|
if (tool.permissionLevel === "explicit") return true;
|
|
@@ -4972,6 +5126,18 @@ ${recipeCtx}`;
|
|
|
4972
5126
|
}
|
|
4973
5127
|
}
|
|
4974
5128
|
const finalEvent = enrichedResult !== toolEvent.result ? { ...toolEvent, result: enrichedResult } : toolEvent;
|
|
5129
|
+
if (!finalEvent.isError && tool) {
|
|
5130
|
+
if (tool.isReadOnly) {
|
|
5131
|
+
const inputForKey = originalCall?.input ?? {};
|
|
5132
|
+
const cacheKey = TurnReadCache.keyFor(finalEvent.toolName, inputForKey);
|
|
5133
|
+
this.turnReadCache.set(cacheKey, {
|
|
5134
|
+
result: finalEvent.result,
|
|
5135
|
+
sourceToolUseId: finalEvent.toolUseId
|
|
5136
|
+
});
|
|
5137
|
+
} else {
|
|
5138
|
+
this.turnReadCache.clear();
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
4975
5141
|
yield finalEvent;
|
|
4976
5142
|
if (finalEvent.type === "tool_result" && !finalEvent.isError) {
|
|
4977
5143
|
const r = finalEvent.result;
|
|
@@ -5048,6 +5214,7 @@ ${recipeCtx}`;
|
|
|
5048
5214
|
const writeGuardInjections = pendingWrite.call._guardInjections;
|
|
5049
5215
|
const modifiableFields = getModifiableFields(pendingWrite.call.name);
|
|
5050
5216
|
const turnIndex = this.messages.filter((m) => m.role === "assistant").length;
|
|
5217
|
+
this.turnPaused = true;
|
|
5051
5218
|
yield {
|
|
5052
5219
|
type: "pending_action",
|
|
5053
5220
|
action: {
|