pi-extensible-workflows 3.4.0 → 3.4.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.
- package/dist/src/agent-execution.js +22 -7
- package/package.json +2 -2
- package/src/agent-execution.ts +13 -5
|
@@ -17,6 +17,7 @@ function text(message) {
|
|
|
17
17
|
return "";
|
|
18
18
|
return message.content.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string").map((part) => part.text).join("");
|
|
19
19
|
}
|
|
20
|
+
function isEmptyAbortedAssistant(message) { return message?.stopReason === "aborted" && Array.isArray(message.content) && message.content.length === 0; }
|
|
20
21
|
function hasToolCall(message) {
|
|
21
22
|
return typeof message === "object" && message !== null && Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall");
|
|
22
23
|
}
|
|
@@ -145,6 +146,11 @@ export async function createLocalPiSession(input) {
|
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
148
|
function workflowAgentMessage(message) { return message ? { role: message.role, ...(message.content === undefined ? {} : { content: message.content }), ...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }), ...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }), ...(message.usage === undefined ? {} : { usage: message.usage }) } : undefined; }
|
|
149
|
+
function latestUsableAssistant(messages) { for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
150
|
+
const candidate = workflowAgentMessage(messages[index]);
|
|
151
|
+
if (candidate?.role === "assistant" && !isEmptyAbortedAssistant(candidate))
|
|
152
|
+
return candidate;
|
|
153
|
+
} return undefined; }
|
|
148
154
|
function workflowAgentStats(stats) { return { tokens: { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, total: stats.tokens.total }, cost: stats.cost }; }
|
|
149
155
|
function workflowAgentState(native, prepared) {
|
|
150
156
|
const tools = native.agent?.state.tools.map(({ name }) => name) ?? prepared.tools;
|
|
@@ -173,11 +179,11 @@ export async function createLocalWorkflowAgentSession(prepared, context) {
|
|
|
173
179
|
getState: () => Object.freeze(workflowAgentState(native, prepared)),
|
|
174
180
|
getSessionStats: () => workflowAgentStats(native.getSessionStats()),
|
|
175
181
|
subscribe(listener) { listener({ type: "state_changed", state: workflowAgentState(native, prepared) }); return native.subscribe?.((event) => { listener(localSessionEvent(event)); }) ?? (() => undefined); },
|
|
176
|
-
getLastAssistant: () =>
|
|
182
|
+
getLastAssistant: () => latestUsableAssistant(native.messages),
|
|
177
183
|
async prompt(text) {
|
|
178
184
|
if (disposed)
|
|
179
185
|
throw new WorkflowError("INTERNAL_ERROR", "Local workflow session is disposed");
|
|
180
|
-
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant =
|
|
186
|
+
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant = latestUsableAssistant(native.messages); return assistant ? { assistant } : {}; });
|
|
181
187
|
prompting = prompt;
|
|
182
188
|
try {
|
|
183
189
|
return await prompt;
|
|
@@ -477,11 +483,20 @@ export class WorkflowAgentExecutor {
|
|
|
477
483
|
setupSummary = { ...setupSummary, disabledAgentResources: resourcePolicySummary(setup.sessionInput.resourcePolicy) };
|
|
478
484
|
const activeSession = session;
|
|
479
485
|
let lastAssistant;
|
|
486
|
+
const acceptAssistant = (candidate) => {
|
|
487
|
+
if (isEmptyAbortedAssistant(candidate)) {
|
|
488
|
+
const previous = activeSession.getLastAssistant?.();
|
|
489
|
+
if (previous && !isEmptyAbortedAssistant(previous))
|
|
490
|
+
lastAssistant = previous;
|
|
491
|
+
}
|
|
492
|
+
else if (candidate)
|
|
493
|
+
lastAssistant = candidate;
|
|
494
|
+
};
|
|
480
495
|
const recoverTerminal = () => recoverTerminalProviderError(activeSession, options.label, options.providerErrorRecovery, async () => { try {
|
|
481
|
-
|
|
496
|
+
acceptAssistant((await promptWithProviderPause(activeSession, providerContinuationPrompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant);
|
|
482
497
|
}
|
|
483
498
|
catch (error) {
|
|
484
|
-
|
|
499
|
+
acceptAssistant(activeSession.getLastAssistant?.() ?? lastAssistant);
|
|
485
500
|
if (!hasSchemaResult())
|
|
486
501
|
throw error;
|
|
487
502
|
} }, () => lastAssistant);
|
|
@@ -489,10 +504,10 @@ export class WorkflowAgentExecutor {
|
|
|
489
504
|
let promptFailed = false;
|
|
490
505
|
let promptError;
|
|
491
506
|
try {
|
|
492
|
-
|
|
507
|
+
acceptAssistant((await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant);
|
|
493
508
|
}
|
|
494
509
|
catch (error) {
|
|
495
|
-
|
|
510
|
+
acceptAssistant(activeSession.getLastAssistant?.() ?? lastAssistant);
|
|
496
511
|
promptFailed = true;
|
|
497
512
|
promptError = error;
|
|
498
513
|
}
|
|
@@ -556,7 +571,7 @@ export class WorkflowAgentExecutor {
|
|
|
556
571
|
activity = undefined;
|
|
557
572
|
shouldReport = activityChanged(previousActivity);
|
|
558
573
|
if (event.message?.role === "assistant") {
|
|
559
|
-
|
|
574
|
+
acceptAssistant(event.message);
|
|
560
575
|
const needsMoreWork = hasToolCall(event.message);
|
|
561
576
|
const final = !needsMoreWork || (options.schema !== undefined && hasSchemaResult());
|
|
562
577
|
if (!budgetError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extensible-workflows",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Deterministic multi-agent workflow orchestration for Pi",
|
|
5
5
|
"homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"inspect": "node dist/src/cli.js inspect",
|
|
34
34
|
"lint": "eslint .",
|
|
35
35
|
"test": "npm run build && TEST_FILES='dist/test/*.test.js' npm run test:run",
|
|
36
|
-
"test:run": "tmp=$(mktemp -d); trap 'rm -rf \"$tmp\"' EXIT; TMPDIR=\"$tmp\" node --test --test-concurrency=1 $TEST_FILES",
|
|
36
|
+
"test:run": "tmp=$(mktemp -d); trap 'rm -rf \"$tmp\"' EXIT; TMPDIR=\"$tmp\" node --test --test-concurrency=1 --test-reporter=dot $TEST_FILES",
|
|
37
37
|
"acceptance": "npm run build && TEST_FILES='dist/test/runtime-acceptance.test.js' npm run test:run",
|
|
38
38
|
"docs:check": "node scripts/check-docs.mjs",
|
|
39
39
|
"check": "npm run lint && npm test && npm run docs:check",
|
package/src/agent-execution.ts
CHANGED
|
@@ -94,6 +94,7 @@ function text(message: WorkflowAgentMessage | undefined): string {
|
|
|
94
94
|
if (!message || !Array.isArray(message.content)) return "";
|
|
95
95
|
return message.content.filter((part: unknown): part is { type: "text"; text: string } => typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string").map((part) => part.text).join("");
|
|
96
96
|
}
|
|
97
|
+
function isEmptyAbortedAssistant(message: WorkflowAgentMessage | undefined): boolean { return message?.stopReason === "aborted" && Array.isArray(message.content) && message.content.length === 0; }
|
|
97
98
|
|
|
98
99
|
function hasToolCall(message: unknown): boolean {
|
|
99
100
|
return typeof message === "object" && message !== null && Array.isArray((message as { content?: unknown }).content) && (message as { content: unknown[] }).content.some((part) => typeof part === "object" && part !== null && (part as { type?: unknown }).type === "toolCall");
|
|
@@ -205,6 +206,7 @@ export async function createLocalPiSession(input: SessionInput): Promise<PiSessi
|
|
|
205
206
|
}) as unknown as PiSession;
|
|
206
207
|
}
|
|
207
208
|
function workflowAgentMessage(message: AgentMessage | undefined): WorkflowAgentMessage | undefined { return message ? { role: message.role, ...(message.content === undefined ? {} : { content: message.content }), ...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }), ...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }), ...(message.usage === undefined ? {} : { usage: message.usage }) } : undefined; }
|
|
209
|
+
function latestUsableAssistant(messages: readonly AgentMessage[]): WorkflowAgentMessage | undefined { for (let index = messages.length - 1; index >= 0; index -= 1) { const candidate = workflowAgentMessage(messages[index]); if (candidate?.role === "assistant" && !isEmptyAbortedAssistant(candidate)) return candidate; } return undefined; }
|
|
208
210
|
function workflowAgentStats(stats: ReturnType<PiSession["getSessionStats"]>): WorkflowAgentSessionStats { return { tokens: { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, total: stats.tokens.total }, cost: stats.cost }; }
|
|
209
211
|
function workflowAgentState(native: PiSession, prepared: Readonly<PreparedAgentSession>): WorkflowAgentSessionState {
|
|
210
212
|
const tools = native.agent?.state.tools.map(({ name }) => name) ?? prepared.tools;
|
|
@@ -233,10 +235,10 @@ export async function createLocalWorkflowAgentSession(prepared: Readonly<Prepare
|
|
|
233
235
|
getState: () => Object.freeze(workflowAgentState(native, prepared)),
|
|
234
236
|
getSessionStats: () => workflowAgentStats(native.getSessionStats()),
|
|
235
237
|
subscribe(listener: (event: WorkflowAgentSessionEvent) => void) { listener({ type: "state_changed", state: workflowAgentState(native, prepared) }); return native.subscribe?.((event) => { listener(localSessionEvent(event)); }) ?? (() => undefined); },
|
|
236
|
-
getLastAssistant: () =>
|
|
238
|
+
getLastAssistant: () => latestUsableAssistant(native.messages),
|
|
237
239
|
async prompt(text: string) {
|
|
238
240
|
if (disposed) throw new WorkflowError("INTERNAL_ERROR", "Local workflow session is disposed");
|
|
239
|
-
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant =
|
|
241
|
+
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant = latestUsableAssistant(native.messages); return assistant ? { assistant } : {}; });
|
|
240
242
|
prompting = prompt;
|
|
241
243
|
try { return await prompt; } finally { if (prompting === prompt) prompting = undefined; }
|
|
242
244
|
},
|
|
@@ -480,11 +482,17 @@ export class WorkflowAgentExecutor {
|
|
|
480
482
|
if (setup.sessionInput.resourcePolicy) setupSummary = { ...setupSummary, disabledAgentResources: resourcePolicySummary(setup.sessionInput.resourcePolicy) };
|
|
481
483
|
const activeSession = session;
|
|
482
484
|
let lastAssistant: WorkflowAgentMessage | undefined;
|
|
483
|
-
const
|
|
485
|
+
const acceptAssistant = (candidate: WorkflowAgentMessage | undefined) => {
|
|
486
|
+
if (isEmptyAbortedAssistant(candidate)) {
|
|
487
|
+
const previous = (activeSession as WorkflowAgentSession & { getLastAssistant?: () => WorkflowAgentMessage | undefined }).getLastAssistant?.();
|
|
488
|
+
if (previous && !isEmptyAbortedAssistant(previous)) lastAssistant = previous;
|
|
489
|
+
} else if (candidate) lastAssistant = candidate;
|
|
490
|
+
};
|
|
491
|
+
const recoverTerminal = () => recoverTerminalProviderError(activeSession, options.label, options.providerErrorRecovery, async () => { try { acceptAssistant((await promptWithProviderPause(activeSession, providerContinuationPrompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant); } catch (error) { acceptAssistant((activeSession as WorkflowAgentSession & { getLastAssistant?: () => WorkflowAgentMessage | undefined }).getLastAssistant?.() ?? lastAssistant); if (!hasSchemaResult()) throw error; } }, () => lastAssistant);
|
|
484
492
|
const promptAndRecover = async (prompt: string): Promise<void> => {
|
|
485
493
|
let promptFailed = false;
|
|
486
494
|
let promptError: unknown;
|
|
487
|
-
try {
|
|
495
|
+
try { acceptAssistant((await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant); } catch (error) { acceptAssistant((activeSession as WorkflowAgentSession & { getLastAssistant?: () => WorkflowAgentMessage | undefined }).getLastAssistant?.() ?? lastAssistant); promptFailed = true; promptError = error; }
|
|
488
496
|
const recovered = await recoverTerminal();
|
|
489
497
|
if (promptFailed && !hasSchemaResult() && !recovered) throw promptError;
|
|
490
498
|
};
|
|
@@ -519,7 +527,7 @@ export class WorkflowAgentExecutor {
|
|
|
519
527
|
activity = undefined;
|
|
520
528
|
shouldReport = activityChanged(previousActivity);
|
|
521
529
|
if (event.message?.role === "assistant") {
|
|
522
|
-
|
|
530
|
+
acceptAssistant(event.message);
|
|
523
531
|
const needsMoreWork = hasToolCall(event.message);
|
|
524
532
|
const final = !needsMoreWork || (options.schema !== undefined && hasSchemaResult());
|
|
525
533
|
if (!budgetError) { try { options.budget?.afterTurn(accounting(activeSession.getSessionStats()), final); if (!final) { const instruction = options.budget?.instruction(); if (instruction) void activeSession.steer(instruction); } } catch (error) { budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error)); void activeSession.abort(); } }
|