@t2000/engine 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +61 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -432,6 +432,16 @@ type EngineEvent = {
|
|
|
432
432
|
* actually re-running the tool.
|
|
433
433
|
*/
|
|
434
434
|
resultDeduped?: boolean;
|
|
435
|
+
/**
|
|
436
|
+
* [v1.5] True when this result was produced by the engine's
|
|
437
|
+
* post-write refresh mechanism (see `EngineConfig.postWriteRefresh`).
|
|
438
|
+
* The engine auto-runs configured read tools immediately after a
|
|
439
|
+
* successful write so the LLM narrates from fresh on-chain state
|
|
440
|
+
* instead of inferring from a stale snapshot. Hosts should render
|
|
441
|
+
* these like any other tool result; the flag is for analytics and
|
|
442
|
+
* UI affordances (e.g. a subtle "auto-refreshed" badge).
|
|
443
|
+
*/
|
|
444
|
+
wasPostWriteRefresh?: boolean;
|
|
435
445
|
} | {
|
|
436
446
|
type: 'pending_action';
|
|
437
447
|
action: PendingAction;
|
|
@@ -699,6 +709,43 @@ interface EngineConfig {
|
|
|
699
709
|
* verdict→action mapping. Errors thrown by the host are caught.
|
|
700
710
|
*/
|
|
701
711
|
onGuardFired?: (guard: GuardMetric) => void;
|
|
712
|
+
/**
|
|
713
|
+
* [v1.5] Map of write tool name → list of read tool names whose state
|
|
714
|
+
* the write invalidates. After a successful write resumes via
|
|
715
|
+
* `resumeWithToolResult`, the engine auto-runs each configured read
|
|
716
|
+
* tool with empty input, pushes synthetic `tool_use` + `tool_result`
|
|
717
|
+
* messages into the conversation, and yields `tool_result` events
|
|
718
|
+
* with `wasPostWriteRefresh: true` BEFORE handing control back to the
|
|
719
|
+
* LLM for narration.
|
|
720
|
+
*
|
|
721
|
+
* Why: writes change on-chain state. Without a fresh read, the LLM
|
|
722
|
+
* narrates from the pre-write snapshot and frequently invents balance
|
|
723
|
+
* totals. Auto-injecting fresh reads makes the hallucination class
|
|
724
|
+
* physically impossible — the model has authoritative ground truth in
|
|
725
|
+
* its context before generating the post-write sentence.
|
|
726
|
+
*
|
|
727
|
+
* Constraints:
|
|
728
|
+
* - Refresh tools MUST be `isReadOnly` and `isConcurrencySafe`.
|
|
729
|
+
* - Refresh runs only when the write succeeded (executionResult is
|
|
730
|
+
* not `{ success: false }`); failed writes leave state unchanged
|
|
731
|
+
* and refreshing would be misleading.
|
|
732
|
+
* - Tools are invoked with empty input; refresh tools should accept
|
|
733
|
+
* an empty object schema (e.g. `balance_check`, `savings_info`).
|
|
734
|
+
* - Errors during refresh are non-fatal — a tool_result with
|
|
735
|
+
* `isError: true` is still pushed so the LLM knows refresh failed.
|
|
736
|
+
*
|
|
737
|
+
* Example:
|
|
738
|
+
* ```
|
|
739
|
+
* {
|
|
740
|
+
* save_deposit: ['balance_check', 'savings_info'],
|
|
741
|
+
* send_transfer: ['balance_check'],
|
|
742
|
+
* borrow: ['balance_check', 'savings_info', 'health_check'],
|
|
743
|
+
* }
|
|
744
|
+
* ```
|
|
745
|
+
*
|
|
746
|
+
* Omit (undefined / empty map) to disable post-write refresh entirely.
|
|
747
|
+
*/
|
|
748
|
+
postWriteRefresh?: Record<string, string[]>;
|
|
702
749
|
}
|
|
703
750
|
interface LLMProvider {
|
|
704
751
|
chat(params: ChatParams): AsyncGenerator<ProviderEvent>;
|
|
@@ -824,6 +871,7 @@ declare class QueryEngine {
|
|
|
824
871
|
private readonly sessionSpendUsd;
|
|
825
872
|
private readonly onAutoExecuted;
|
|
826
873
|
private readonly onGuardFired;
|
|
874
|
+
private readonly postWriteRefresh;
|
|
827
875
|
private matchedRecipe;
|
|
828
876
|
private messages;
|
|
829
877
|
private abortController;
|
|
@@ -845,6 +893,16 @@ declare class QueryEngine {
|
|
|
845
893
|
* This is a separate HTTP request — no persistent connection from submitMessage.
|
|
846
894
|
*/
|
|
847
895
|
resumeWithToolResult(action: PendingAction, response: PermissionResponse): AsyncGenerator<EngineEvent>;
|
|
896
|
+
/**
|
|
897
|
+
* [v1.5] Auto-run configured read tools after a successful write,
|
|
898
|
+
* push their results into the conversation, and yield `tool_result`
|
|
899
|
+
* events so hosts/UI render them in the timeline. See
|
|
900
|
+
* `EngineConfig.postWriteRefresh`.
|
|
901
|
+
*
|
|
902
|
+
* Pure injection — no LLM call here. The next `agentLoop` turn sees
|
|
903
|
+
* the fresh tool results and narrates from them.
|
|
904
|
+
*/
|
|
905
|
+
private runPostWriteRefresh;
|
|
848
906
|
interrupt(): void;
|
|
849
907
|
getMessages(): readonly Message[];
|
|
850
908
|
getMatchedRecipe(): Recipe | null;
|
|
@@ -916,6 +974,9 @@ type SSEEvent = {
|
|
|
916
974
|
toolUseId: string;
|
|
917
975
|
result: unknown;
|
|
918
976
|
isError: boolean;
|
|
977
|
+
wasEarlyDispatched?: boolean;
|
|
978
|
+
resultDeduped?: boolean;
|
|
979
|
+
wasPostWriteRefresh?: boolean;
|
|
919
980
|
} | {
|
|
920
981
|
type: 'pending_action';
|
|
921
982
|
action: PendingAction;
|
package/dist/index.js
CHANGED
|
@@ -4177,6 +4177,9 @@ var QueryEngine = class {
|
|
|
4177
4177
|
sessionSpendUsd;
|
|
4178
4178
|
onAutoExecuted;
|
|
4179
4179
|
onGuardFired;
|
|
4180
|
+
// [v1.5] See `EngineConfig.postWriteRefresh` — drives the post-write
|
|
4181
|
+
// synthetic read injection in `resumeWithToolResult`.
|
|
4182
|
+
postWriteRefresh;
|
|
4180
4183
|
matchedRecipe = null;
|
|
4181
4184
|
messages = [];
|
|
4182
4185
|
abortController = null;
|
|
@@ -4209,6 +4212,7 @@ var QueryEngine = class {
|
|
|
4209
4212
|
this.sessionSpendUsd = config.sessionSpendUsd;
|
|
4210
4213
|
this.onAutoExecuted = config.onAutoExecuted;
|
|
4211
4214
|
this.onGuardFired = config.onGuardFired;
|
|
4215
|
+
this.postWriteRefresh = config.postWriteRefresh;
|
|
4212
4216
|
this.tools = config.tools ?? (config.agent ? getDefaultTools() : []);
|
|
4213
4217
|
}
|
|
4214
4218
|
/**
|
|
@@ -4277,8 +4281,96 @@ var QueryEngine = class {
|
|
|
4277
4281
|
yield { type: "turn_complete", stopReason: "end_turn" };
|
|
4278
4282
|
return;
|
|
4279
4283
|
}
|
|
4284
|
+
yield* this.runPostWriteRefresh(action, response, signal);
|
|
4280
4285
|
yield* this.agentLoop(null, signal, false);
|
|
4281
4286
|
}
|
|
4287
|
+
/**
|
|
4288
|
+
* [v1.5] Auto-run configured read tools after a successful write,
|
|
4289
|
+
* push their results into the conversation, and yield `tool_result`
|
|
4290
|
+
* events so hosts/UI render them in the timeline. See
|
|
4291
|
+
* `EngineConfig.postWriteRefresh`.
|
|
4292
|
+
*
|
|
4293
|
+
* Pure injection — no LLM call here. The next `agentLoop` turn sees
|
|
4294
|
+
* the fresh tool results and narrates from them.
|
|
4295
|
+
*/
|
|
4296
|
+
async *runPostWriteRefresh(action, response, signal) {
|
|
4297
|
+
const refreshList = this.postWriteRefresh?.[action.toolName];
|
|
4298
|
+
if (!refreshList || refreshList.length === 0) return;
|
|
4299
|
+
const exec = response.executionResult;
|
|
4300
|
+
const writeFailed = exec != null && typeof exec === "object" && "success" in exec && exec.success === false;
|
|
4301
|
+
if (writeFailed) return;
|
|
4302
|
+
const refreshTools = refreshList.map((name) => findTool(this.tools, name)).filter(
|
|
4303
|
+
(t) => t !== void 0 && t.isReadOnly && t.isConcurrencySafe
|
|
4304
|
+
);
|
|
4305
|
+
if (refreshTools.length === 0) return;
|
|
4306
|
+
const context = {
|
|
4307
|
+
agent: this.agent,
|
|
4308
|
+
mcpManager: this.mcpManager,
|
|
4309
|
+
walletAddress: this.walletAddress,
|
|
4310
|
+
suiRpcUrl: this.suiRpcUrl,
|
|
4311
|
+
serverPositions: this.serverPositions,
|
|
4312
|
+
positionFetcher: this.positionFetcher,
|
|
4313
|
+
env: this.env,
|
|
4314
|
+
signal,
|
|
4315
|
+
priceCache: this.priceCache,
|
|
4316
|
+
permissionConfig: this.permissionConfig,
|
|
4317
|
+
sessionSpendUsd: this.sessionSpendUsd
|
|
4318
|
+
};
|
|
4319
|
+
const idStem = `pwr_${action.toolUseId.slice(-6)}`;
|
|
4320
|
+
const refreshes = await Promise.all(
|
|
4321
|
+
refreshTools.map(async (tool, idx) => {
|
|
4322
|
+
const id = `${idStem}_${idx}_${tool.name}`;
|
|
4323
|
+
try {
|
|
4324
|
+
const parsed = tool.inputSchema.safeParse({});
|
|
4325
|
+
if (!parsed.success) {
|
|
4326
|
+
return {
|
|
4327
|
+
tool,
|
|
4328
|
+
id,
|
|
4329
|
+
isError: true,
|
|
4330
|
+
data: {
|
|
4331
|
+
error: `Post-write refresh: invalid input for ${tool.name}`
|
|
4332
|
+
}
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
const result = await tool.call(parsed.data, context);
|
|
4336
|
+
return { tool, id, isError: false, data: result.data };
|
|
4337
|
+
} catch (err) {
|
|
4338
|
+
return {
|
|
4339
|
+
tool,
|
|
4340
|
+
id,
|
|
4341
|
+
isError: true,
|
|
4342
|
+
data: {
|
|
4343
|
+
error: err instanceof Error ? err.message : "Post-write refresh failed"
|
|
4344
|
+
}
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
})
|
|
4348
|
+
);
|
|
4349
|
+
const refreshUses = refreshes.map((r) => ({
|
|
4350
|
+
type: "tool_use",
|
|
4351
|
+
id: r.id,
|
|
4352
|
+
name: r.tool.name,
|
|
4353
|
+
input: {}
|
|
4354
|
+
}));
|
|
4355
|
+
this.messages.push({ role: "assistant", content: refreshUses });
|
|
4356
|
+
const refreshResults = refreshes.map((r) => ({
|
|
4357
|
+
type: "tool_result",
|
|
4358
|
+
toolUseId: r.id,
|
|
4359
|
+
content: typeof r.data === "string" ? r.data : JSON.stringify(r.data),
|
|
4360
|
+
isError: r.isError
|
|
4361
|
+
}));
|
|
4362
|
+
this.messages.push({ role: "user", content: refreshResults });
|
|
4363
|
+
for (const r of refreshes) {
|
|
4364
|
+
yield {
|
|
4365
|
+
type: "tool_result",
|
|
4366
|
+
toolName: r.tool.name,
|
|
4367
|
+
toolUseId: r.id,
|
|
4368
|
+
result: r.data,
|
|
4369
|
+
isError: r.isError,
|
|
4370
|
+
wasPostWriteRefresh: true
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4282
4374
|
interrupt() {
|
|
4283
4375
|
this.abortController?.abort();
|
|
4284
4376
|
}
|