@caupulican/pi-agent-core 0.85.3 → 0.85.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/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +128 -24
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +4 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -3
- package/dist/agent.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +1 -0
- package/dist/tool-failure-memory.d.ts.map +1 -1
- package/dist/tool-failure-memory.js +4 -0
- package/dist/tool-failure-memory.js.map +1 -1
- package/package.json +92 -4
package/dist/agent-loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAyB7D,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EAGb,uBAAuB,EACvB,sBAAsB,EACtB,QAAQ,EACR,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAIzC;AAuBD,wBAAsB,YAAY,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBzB;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAWzB;AAyFD;;;;GAIG;AACH,wBAAsB,gCAAgC,CAAC,OAAO,EAAE;IAC/D,gBAAgB,CAAC,EAAE,CAClB,OAAO,EAAE,uBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,KAChB,sBAAsB,GAAG,SAAS,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAC;IACtF,KAAK,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,EAAE,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB9B;AAmLD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CA8CxC;AA47BD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAO7F"}
|
package/dist/agent-loop.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { EventStream } from "@caupulican/pi-ai/event-stream";
|
|
6
6
|
import { streamSimple } from "@caupulican/pi-ai/stream";
|
|
7
|
-
import { formatToolRepairStandingRule } from "@caupulican/pi-ai/tool-repair-registry";
|
|
7
|
+
import { formatToolRepairStandingRule, REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE, } from "@caupulican/pi-ai/tool-repair-registry";
|
|
8
8
|
import { ToolArgumentValidationError, validateToolArguments, } from "@caupulican/pi-ai/validation";
|
|
9
9
|
import { assessToolFailure, clearToolFailure, createToolFailureMemoryTracker, createToolFailureResult, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
|
|
10
10
|
import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
|
|
@@ -98,6 +98,51 @@ function createAgentStream() {
|
|
|
98
98
|
* enough that it's indistinguishable from legitimate varied work, so we don't chase it.
|
|
99
99
|
*/
|
|
100
100
|
const STALL_WINDOW_PERIODS = 4;
|
|
101
|
+
function textProtocolOperationArguments(toolCall) {
|
|
102
|
+
const args = toolCall.arguments;
|
|
103
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
104
|
+
return args ?? null;
|
|
105
|
+
const record = args;
|
|
106
|
+
if ((toolCall.name === "write" || toolCall.name === "edit") && typeof record.path === "string") {
|
|
107
|
+
return { path: record.path };
|
|
108
|
+
}
|
|
109
|
+
return args;
|
|
110
|
+
}
|
|
111
|
+
function textProtocolBatchSignature(toolCalls) {
|
|
112
|
+
return normalizeToolSignature(toolCalls.map((toolCall) => [toolCall.name, textProtocolOperationArguments(toolCall)]));
|
|
113
|
+
}
|
|
114
|
+
function toolCallRecord(toolCall) {
|
|
115
|
+
const args = toolCall.arguments;
|
|
116
|
+
return args && typeof args === "object" && !Array.isArray(args) ? args : undefined;
|
|
117
|
+
}
|
|
118
|
+
function toolResultPhase(result) {
|
|
119
|
+
if (!result.details || typeof result.details !== "object" || Array.isArray(result.details))
|
|
120
|
+
return undefined;
|
|
121
|
+
const phase = result.details.phase;
|
|
122
|
+
return typeof phase === "string" ? phase : undefined;
|
|
123
|
+
}
|
|
124
|
+
function repeatsSuccessfulTextProtocolBatch(incomingSignature, toolCalls, previous) {
|
|
125
|
+
if (!previous)
|
|
126
|
+
return false;
|
|
127
|
+
if (incomingSignature === previous.signature)
|
|
128
|
+
return true;
|
|
129
|
+
if (toolCalls.length !== previous.calls.length || toolCalls.length !== previous.messages.length)
|
|
130
|
+
return false;
|
|
131
|
+
return toolCalls.every((toolCall, index) => {
|
|
132
|
+
const previousCall = previous.calls[index];
|
|
133
|
+
const previousResult = previous.messages[index];
|
|
134
|
+
if (!previousCall || !previousResult)
|
|
135
|
+
return false;
|
|
136
|
+
const currentArgs = toolCallRecord(toolCall);
|
|
137
|
+
const previousArgs = toolCallRecord(previousCall);
|
|
138
|
+
if (currentArgs?.path !== previousArgs?.path)
|
|
139
|
+
return false;
|
|
140
|
+
const phase = toolResultPhase(previousResult);
|
|
141
|
+
if (phase === "written")
|
|
142
|
+
return toolCall.name === "write" && previousCall.name === "write";
|
|
143
|
+
return phase === "edited" && toolCall.name === "edit" && previousCall.name === "edit";
|
|
144
|
+
});
|
|
145
|
+
}
|
|
101
146
|
/**
|
|
102
147
|
* Apply one request-local preflight without mutating persistent loop configuration.
|
|
103
148
|
* Shared with isolated tool-free provider calls so every transport boundary has identical
|
|
@@ -144,6 +189,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
144
189
|
const validationFailureTracker = { repeats: 0 };
|
|
145
190
|
const repairTeachTracker = new Map();
|
|
146
191
|
let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
|
|
192
|
+
let lastSuccessfulTextProtocolBatch;
|
|
147
193
|
// Check for steering messages at start (user may have typed while waiting)
|
|
148
194
|
let pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
149
195
|
// Outer loop: continues when queued follow-up messages arrive after agent would stop
|
|
@@ -159,6 +205,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
159
205
|
}
|
|
160
206
|
// Process pending messages (inject before next assistant response)
|
|
161
207
|
if (pendingMessages.length > 0) {
|
|
208
|
+
lastSuccessfulTextProtocolBatch = undefined;
|
|
162
209
|
for (const message of pendingMessages) {
|
|
163
210
|
await emit({ type: "message_start", message });
|
|
164
211
|
await emit({ type: "message_end", message });
|
|
@@ -180,13 +227,34 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
180
227
|
const toolResults = [];
|
|
181
228
|
hasMoreToolCalls = false;
|
|
182
229
|
if (toolCalls.length > 0) {
|
|
183
|
-
const
|
|
230
|
+
const textProtocolBatch = toolCalls.every((toolCall) => toolCall.source === "text-protocol");
|
|
231
|
+
const incomingBatchSignature = textProtocolBatchSignature(toolCalls);
|
|
232
|
+
const previousSuccessfulTextProtocolResults = textProtocolBatch &&
|
|
233
|
+
repeatsSuccessfulTextProtocolBatch(incomingBatchSignature, toolCalls, lastSuccessfulTextProtocolBatch)
|
|
234
|
+
? lastSuccessfulTextProtocolBatch?.messages
|
|
235
|
+
: undefined;
|
|
236
|
+
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
184
237
|
toolResults.push(...executedToolBatch.messages);
|
|
185
238
|
hasMoreToolCalls = !executedToolBatch.terminate;
|
|
186
239
|
for (const result of toolResults) {
|
|
187
240
|
currentContext.messages.push(result);
|
|
188
241
|
newMessages.push(result);
|
|
189
242
|
}
|
|
243
|
+
if (!previousSuccessfulTextProtocolResults) {
|
|
244
|
+
lastSuccessfulTextProtocolBatch =
|
|
245
|
+
textProtocolBatch &&
|
|
246
|
+
toolResults.length === toolCalls.length &&
|
|
247
|
+
toolResults.every((result) => !result.isError)
|
|
248
|
+
? {
|
|
249
|
+
signature: textProtocolBatchSignature(toolCalls),
|
|
250
|
+
messages: toolResults,
|
|
251
|
+
calls: toolCalls,
|
|
252
|
+
}
|
|
253
|
+
: undefined;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
lastSuccessfulTextProtocolBatch = undefined;
|
|
190
258
|
}
|
|
191
259
|
await emit({ type: "turn_end", message, toolResults });
|
|
192
260
|
// Runaway-loop backstop (cost guard): detect a model stuck repeating one action.
|
|
@@ -358,27 +426,36 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
358
426
|
/**
|
|
359
427
|
* Execute tool calls from an assistant message.
|
|
360
428
|
*/
|
|
361
|
-
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
|
|
429
|
+
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
362
430
|
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
363
431
|
const hasSequentialToolCall = toolCalls.some((tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential");
|
|
364
432
|
if (config.toolExecution === "sequential" || hasSequentialToolCall) {
|
|
365
|
-
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
433
|
+
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
366
434
|
}
|
|
367
|
-
return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
435
|
+
return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
368
436
|
}
|
|
369
|
-
async function
|
|
437
|
+
async function prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
438
|
+
const preparation = previousSuccessfulTextProtocolResults
|
|
439
|
+
? createRepeatedSuccessfulToolCallOutcome(previousSuccessfulTextProtocolResults[index])
|
|
440
|
+
: await prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal);
|
|
441
|
+
await emitToolExecutionStart(toolCall, emit);
|
|
442
|
+
if (preparation.kind === "immediate") {
|
|
443
|
+
emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
|
|
444
|
+
return { kind: "finalized", finalized: finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory) };
|
|
445
|
+
}
|
|
446
|
+
return { kind: "prepared", preparation };
|
|
447
|
+
}
|
|
448
|
+
async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
370
449
|
const finalizedCalls = [];
|
|
371
450
|
const messages = [];
|
|
372
|
-
for (const toolCall of toolCalls) {
|
|
373
|
-
const
|
|
374
|
-
await emitToolExecutionStart(toolCall, emit);
|
|
451
|
+
for (const [index, toolCall] of toolCalls.entries()) {
|
|
452
|
+
const started = await prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
375
453
|
let finalized;
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
|
|
454
|
+
if (started.kind === "finalized") {
|
|
455
|
+
finalized = started.finalized;
|
|
379
456
|
}
|
|
380
457
|
else {
|
|
381
|
-
finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
458
|
+
finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, started.preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
382
459
|
}
|
|
383
460
|
await emitToolExecutionEnd(finalized, emit);
|
|
384
461
|
const toolResultMessage = createToolResultMessage(finalized);
|
|
@@ -394,23 +471,20 @@ async function executeToolCallsSequential(currentContext, assistantMessage, tool
|
|
|
394
471
|
terminate: shouldTerminateToolBatch(finalizedCalls),
|
|
395
472
|
};
|
|
396
473
|
}
|
|
397
|
-
async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
|
|
474
|
+
async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit) {
|
|
398
475
|
const finalizedCalls = [];
|
|
399
|
-
for (const toolCall of toolCalls) {
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
|
|
405
|
-
await emitToolExecutionEnd(finalized, emit);
|
|
406
|
-
finalizedCalls.push(finalized);
|
|
476
|
+
for (const [index, toolCall] of toolCalls.entries()) {
|
|
477
|
+
const started = await prepareAndStartToolCall(currentContext, assistantMessage, toolCall, index, config, validationFailureTracker, toolFailureMemory, previousSuccessfulTextProtocolResults, signal, emit);
|
|
478
|
+
if (started.kind === "finalized") {
|
|
479
|
+
await emitToolExecutionEnd(started.finalized, emit);
|
|
480
|
+
finalizedCalls.push(started.finalized);
|
|
407
481
|
if (signal?.aborted) {
|
|
408
482
|
break;
|
|
409
483
|
}
|
|
410
484
|
continue;
|
|
411
485
|
}
|
|
412
486
|
finalizedCalls.push(async () => {
|
|
413
|
-
const finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
487
|
+
const finalized = await executeAndFinalizePreparedToolCall(currentContext, assistantMessage, started.preparation, config, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
414
488
|
await emitToolExecutionEnd(finalized, emit);
|
|
415
489
|
return finalized;
|
|
416
490
|
});
|
|
@@ -432,6 +506,27 @@ async function executeToolCallsParallel(currentContext, assistantMessage, toolCa
|
|
|
432
506
|
}
|
|
433
507
|
const DEFAULT_TOOL_VALIDATION_ESCALATION_THRESHOLD = 3;
|
|
434
508
|
const TOOL_REPAIR_TEACH_EVERY = 5;
|
|
509
|
+
const REPEATED_SUCCESS_RESULT_MAX_CHARS = 2_048;
|
|
510
|
+
function createRepeatedSuccessfulToolCallOutcome(previousResult) {
|
|
511
|
+
const previousText = previousResult?.content
|
|
512
|
+
.filter((block) => block.type === "text")
|
|
513
|
+
.map((block) => block.text)
|
|
514
|
+
.join("\n")
|
|
515
|
+
.slice(0, REPEATED_SUCCESS_RESULT_MAX_CHARS);
|
|
516
|
+
const diagnostic = previousText
|
|
517
|
+
? `${REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.diagnostic} Previous successful result: ${previousText}`
|
|
518
|
+
: REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.diagnostic;
|
|
519
|
+
return {
|
|
520
|
+
kind: "immediate",
|
|
521
|
+
result: createErrorToolResult(diagnostic),
|
|
522
|
+
isError: true,
|
|
523
|
+
phase: "execution",
|
|
524
|
+
failureCode: REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.failureCode,
|
|
525
|
+
correction: REPEATED_SUCCESSFUL_TOOL_CALL_FAILURE.guidance,
|
|
526
|
+
diagnostic,
|
|
527
|
+
...(previousResult ? { repeatedSuccessfulCall: { previousToolCallId: previousResult.toolCallId } } : {}),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
435
530
|
function shouldTerminateToolBatch(finalizedCalls) {
|
|
436
531
|
return finalizedCalls.length > 0 && finalizedCalls.every((finalized) => finalized.result.terminate === true);
|
|
437
532
|
}
|
|
@@ -642,9 +737,18 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
642
737
|
}
|
|
643
738
|
function finalizeRejectedToolCall(toolCall, outcome, tracker) {
|
|
644
739
|
const record = rememberToolFailure(tracker, toolCall.name, toolCall.arguments, "rejected", outcome.failureCode, outcome.correction, outcome.diagnostic, outcome.phase);
|
|
740
|
+
const failureResult = createToolFailureResult(record, outcome.result.terminate);
|
|
645
741
|
return {
|
|
646
742
|
toolCall,
|
|
647
|
-
result:
|
|
743
|
+
result: outcome.repeatedSuccessfulCall
|
|
744
|
+
? {
|
|
745
|
+
...failureResult,
|
|
746
|
+
details: {
|
|
747
|
+
...failureResult.details,
|
|
748
|
+
piRepeatedSuccessfulCall: outcome.repeatedSuccessfulCall,
|
|
749
|
+
},
|
|
750
|
+
}
|
|
751
|
+
: failureResult,
|
|
648
752
|
isError: true,
|
|
649
753
|
};
|
|
650
754
|
}
|