PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
extendify / src / Agent / follow-ups / pick-next.js

pick-next.js in Extendify 3.2.2, at src/Agent/follow-ups/pick-next.js

80 lines 2.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { recent, useFollowUpHistory } from '@agent/follow-ups/history';
2 import { cards } from '@agent/follow-ups/registry';
3 import { useChatStore } from '@agent/state/chat';
4
5 // An error ends a run without a workflow message.
6 export const toolCallsThisRun = (messages) => {
7 const start = messages.findLastIndex(
8 ({ type, details }) =>
9 type === 'workflow' || (type === 'message' && details?.error),
10 );
11 return messages
12 .slice(start + 1)
13 .filter(
14 ({ type, details }) =>
15 type === 'tool' && 'result' in details && !details.result?.error,
16 )
17 .map(({ details }) => details);
18 };
19
20 const evaluate = (card, ctx) => {
21 try {
22 const data = card.data?.(ctx);
23 return { id: card.id, data, score: card.score({ ...ctx, data }) };
24 } catch (error) {
25 // A throw here would drop the run's finish message.
26 window.extSharedData?.devbuild && console.error(error);
27 return { id: card.id, score: 0 };
28 }
29 };
30
31 const isUserMessage = ({ type, details }) =>
32 type === 'message' && details?.role === 'user';
33
34 export const cardMessageIndex = (messages) => {
35 const index = messages.findLastIndex(({ type }) => type === 'workflow');
36 if (index < 0 || messages.slice(index + 1).some(isUserMessage)) return -1;
37 return index;
38 };
39
40 // A card the user took, or whose task they just did, drops behind the rest.
41 const TAKEN = 0.1;
42
43 export const pickNextCard = ({
44 workflowId,
45 status,
46 messages = useChatStore.getState().messages,
47 }) => {
48 const { context, abilities } = window.extAgentData ?? {};
49 const { cards: history, workflows: done = {} } =
50 useFollowUpHistory.getState();
51 const toolCalls = toolCallsThisRun(messages);
52 const tools = toolCalls.map(({ id }) => id);
53 const ctx = { workflowId, status, tools, toolCalls, context, abilities };
54 const [best] = cards
55 .filter(
56 ({ id, follows, statuses = ['completed'] }) =>
57 !history[id]?.dismissedAt &&
58 (follows === '*' || follows.includes(workflowId)) &&
59 statuses.includes(status),
60 )
61 .map((card) => {
62 const entry = history[card.id] ?? {};
63 const result = evaluate(card, { ...ctx, history: entry });
64 const taken =
65 recent(entry.clickedAt) || recent(done[card.action?.workflowId]);
66 return {
67 ...result,
68 score: taken ? result.score * TAKEN : result.score,
69 shownAt: entry.shownAt ?? 0,
70 order: card.order ?? 0,
71 };
72 })
73 .filter(({ score }) => score > 0)
74 // Equal scores rotate: the card shown longest ago goes first.
75 .toSorted(
76 (a, b) => b.score - a.score || a.shownAt - b.shownAt || a.order - b.order,
77 );
78 return best ?? null;
79 };
80