portable-agent-layer 0.85.5 → 0.85.6
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/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { median } from "./interaction-mood";
|
|
9
|
+
import { approvalIsKnown } from "./interaction-reaction";
|
|
9
10
|
|
|
10
11
|
const MIN_REPLIES_PER_SIDE = 20;
|
|
11
12
|
const MIN_GAP = 0.1;
|
|
@@ -20,6 +21,7 @@ interface ReplyFeatures {
|
|
|
20
21
|
interface ReactedTurn {
|
|
21
22
|
reply: ReplyFeatures | null;
|
|
22
23
|
reaction: string | null;
|
|
24
|
+
reactionRules?: number;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
interface Outcome {
|
|
@@ -41,7 +43,9 @@ export interface Split {
|
|
|
41
43
|
|
|
42
44
|
function outcomes(events: ReactedTurn[]): Outcome[] {
|
|
43
45
|
return events.flatMap((e) =>
|
|
44
|
-
e.reply && e.reaction
|
|
46
|
+
e.reply && e.reaction && approvalIsKnown(e)
|
|
47
|
+
? [{ reply: e.reply, approved: e.reaction === "approved" }]
|
|
48
|
+
: []
|
|
45
49
|
);
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -6,7 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
import { extractKeywords, similarity } from "./text-similarity";
|
|
8
8
|
|
|
9
|
-
export type Reaction =
|
|
9
|
+
export type Reaction =
|
|
10
|
+
| "corrected"
|
|
11
|
+
| "repeated"
|
|
12
|
+
| "approved"
|
|
13
|
+
| "go-ahead"
|
|
14
|
+
| "new-topic"
|
|
15
|
+
| "follow-up";
|
|
16
|
+
|
|
17
|
+
/** Logged with each reaction, so a log read under older rules is not read with today's meaning. */
|
|
18
|
+
export const REACTION_RULES = 2;
|
|
19
|
+
const GO_AHEAD_SPLIT = 2;
|
|
20
|
+
|
|
21
|
+
export function approvalIsKnown(turn: { reactionRules?: number }): boolean {
|
|
22
|
+
return (turn.reactionRules ?? 1) >= GO_AHEAD_SPLIT;
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
const CORRECTION_RE =
|
|
12
26
|
/^(?:(?:no|nope|wrong)(?:[,.!]|$)|(?:that'?s|this is|it'?s) (?:wrong|not)\b|not what i\b|i said\b|i told you\b|you forgot\b|you missed\b|still (?:broken|failing|wrong)\b|i don'?t see (?:the|your) (?!(?:problem|issue|point|harm|difference|need|reason)\b))/i;
|
|
@@ -15,7 +29,10 @@ const CLAIM_DISPUTED_RE =
|
|
|
15
29
|
/\byou(?:'re| are) (?:partially |partly |completely |totally )?wrong\b|\b(?:isn'?t|wasn'?t|aren'?t|weren'?t) (?:added|included|fixed|pushed|committed|applied|merged)\b/i;
|
|
16
30
|
|
|
17
31
|
const APPROVAL_RE =
|
|
18
|
-
/^(?:(?:understood|got it|ok(?:ay)?),?\s+)?(?:good|great|nice|perfect|cool|amazing|awesome|excellent|brilliant|works|it works|thanks|thank you|thx|y+e+s+|yep|yeah|yup|sure|exactly|correct|agreed|lgtm|well done|love it|sounds good|looks good|that'?s it|(?:it|this|that)
|
|
32
|
+
/^(?:(?:understood|got it|ok(?:ay)?),?\s+)?(?:good|great|nice|perfect|cool|amazing|awesome|excellent|brilliant|works|it works|thanks|thank you|thx|y+e+s+|yep|yeah|yup|sure|exactly|correct|agreed|lgtm|well done|love it|sounds good|looks good|that'?s it|(?:it|this|that)(?: is|'s) (?:fine|good|great|right))\b/i;
|
|
33
|
+
|
|
34
|
+
const PRAISE_RE =
|
|
35
|
+
/\b(?:good|great|nice|perfect|cool|amazing|awesome|excellent|brilliant|works|thanks|thank you|thx|exactly|correct|agreed|lgtm|well done|good job|love it|sounds good|looks (?:good|right)|that'?s it|(?:it|this|that)(?: is|'s) (?:fine|good|great|right))\b/i;
|
|
19
36
|
|
|
20
37
|
const GO_AHEAD_RE =
|
|
21
38
|
/^(?:go ahead|go|do it|proceed|continue|merge|push|ship|commit|build|start|fix (?:all|them|both)|approved|let'?s (?:see|do|go|build|start|fix|ship)|(?:add|do|fix) (?:it )?(?:pls|please))\b/i;
|
|
@@ -51,7 +68,7 @@ function sentences(text: string): string[] {
|
|
|
51
68
|
.filter(Boolean);
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
function
|
|
71
|
+
function isAcceptance(text: string): boolean {
|
|
55
72
|
const trimmed = text.trim();
|
|
56
73
|
if (trimmed.includes("?") || FAILURE_RE.test(trimmed)) return false;
|
|
57
74
|
if (HEDGE_RE.test(trimmed.split(/[.!?\n]/)[0])) return false;
|
|
@@ -61,6 +78,11 @@ function isApproval(text: string): boolean {
|
|
|
61
78
|
return isShortGoAhead(all[0] ?? "") && !HEDGE_RE.test(trimmed);
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
/** A yes or a go only gives permission to continue; approval judges the work itself. */
|
|
82
|
+
function praisesTheResult(text: string): boolean {
|
|
83
|
+
return PRAISE_RE.test(text);
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
/** Word overlap was measured against real messages and does not separate topics; only saying so does. */
|
|
65
87
|
function isNewTopic(text: string): boolean {
|
|
66
88
|
return NEW_TOPIC_RE.test(text.trim());
|
|
@@ -69,7 +91,7 @@ function isNewTopic(text: string): boolean {
|
|
|
69
91
|
export function reactionTo(text: string, previousPrompt: string | undefined): Reaction {
|
|
70
92
|
if (isCorrection(text)) return "corrected";
|
|
71
93
|
if (isRepeat(text, previousPrompt)) return "repeated";
|
|
72
|
-
if (
|
|
94
|
+
if (isAcceptance(text)) return praisesTheResult(text) ? "approved" : "go-ahead";
|
|
73
95
|
if (isNewTopic(text)) return "new-topic";
|
|
74
96
|
return "follow-up";
|
|
75
97
|
}
|
|
@@ -26,6 +26,7 @@ import { shorterApprovedMore } from "./interaction-preferences";
|
|
|
26
26
|
import {
|
|
27
27
|
isCorrection,
|
|
28
28
|
isRepeat,
|
|
29
|
+
REACTION_RULES,
|
|
29
30
|
type Reaction,
|
|
30
31
|
reactionTo,
|
|
31
32
|
} from "./interaction-reaction";
|
|
@@ -51,6 +52,7 @@ export interface TurnEvent extends MoodTurn {
|
|
|
51
52
|
weekday: string;
|
|
52
53
|
reply: Omit<ReplyShape, "at"> | null;
|
|
53
54
|
reaction: Reaction | null;
|
|
55
|
+
reactionRules?: number;
|
|
54
56
|
mood?: string;
|
|
55
57
|
hinted?: boolean;
|
|
56
58
|
complied?: boolean;
|
|
@@ -129,6 +131,7 @@ function measureTurn(
|
|
|
129
131
|
corrected: isCorrection(text),
|
|
130
132
|
reply: reply ? replyFeatures(reply) : null,
|
|
131
133
|
reaction: reply ? reactionTo(text, track.lastPrompt) : null,
|
|
134
|
+
...(reply && { reactionRules: REACTION_RULES }),
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -14,13 +14,14 @@ import {
|
|
|
14
14
|
type Split,
|
|
15
15
|
splits,
|
|
16
16
|
} from "../../hooks/lib/interaction-preferences";
|
|
17
|
+
import { approvalIsKnown } from "../../hooks/lib/interaction-reaction";
|
|
17
18
|
|
|
18
19
|
const LABELS = ["short", "long", "fast", "skimming", "friction"] as const;
|
|
19
20
|
|
|
20
21
|
interface ReplyUnderMood {
|
|
21
22
|
labels: string[];
|
|
22
23
|
words: number;
|
|
23
|
-
approved: boolean;
|
|
24
|
+
approved: boolean | null;
|
|
24
25
|
corrected: boolean;
|
|
25
26
|
complied?: boolean;
|
|
26
27
|
}
|
|
@@ -52,6 +53,7 @@ export interface InteractionSummary {
|
|
|
52
53
|
reactions: Record<string, number>;
|
|
53
54
|
hints: Record<string, number>;
|
|
54
55
|
unlogged: number;
|
|
56
|
+
readBeforeGoAhead: number;
|
|
55
57
|
usual: ReplyStats;
|
|
56
58
|
byLabel: Record<string, ReplyStats>;
|
|
57
59
|
approvals: Approvals;
|
|
@@ -81,7 +83,10 @@ function stats(replies: ReplyUnderMood[]): ReplyStats {
|
|
|
81
83
|
return {
|
|
82
84
|
replies: replies.length,
|
|
83
85
|
medianWords: median(replies.map((r) => r.words)),
|
|
84
|
-
approvedShare: share(
|
|
86
|
+
approvedShare: share(
|
|
87
|
+
replies.filter((r) => r.approved !== null),
|
|
88
|
+
(r) => r.approved === true
|
|
89
|
+
),
|
|
85
90
|
correctedShare: share(replies, (r) => r.corrected),
|
|
86
91
|
followed: {
|
|
87
92
|
checked: replies.filter((r) => r.complied !== undefined).length,
|
|
@@ -100,7 +105,7 @@ function repliesUnderMood(events: TurnEvent[]): ReplyUnderMood[] {
|
|
|
100
105
|
replies.push({
|
|
101
106
|
labels: mood ? mood.split(",") : [],
|
|
102
107
|
words: e.reply.words,
|
|
103
|
-
approved: e.reaction === "approved",
|
|
108
|
+
approved: approvalIsKnown(e) ? e.reaction === "approved" : null,
|
|
104
109
|
corrected: e.reaction === "corrected" || e.reaction === "repeated",
|
|
105
110
|
complied: e.complied,
|
|
106
111
|
});
|
|
@@ -109,6 +114,11 @@ function repliesUnderMood(events: TurnEvent[]): ReplyUnderMood[] {
|
|
|
109
114
|
return replies;
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
function reactionName(e: TurnEvent): string {
|
|
118
|
+
if (e.reaction === "approved" && !approvalIsKnown(e)) return "approved or go-ahead";
|
|
119
|
+
return e.reaction ?? "";
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
/** A turn carries the reply that answered the turn before it, so a filed reply shows up there. */
|
|
113
123
|
function byAgent(events: TurnEvent[]): Record<string, AgentCounts> {
|
|
114
124
|
const agents: Record<string, AgentCounts> = {};
|
|
@@ -131,7 +141,8 @@ export function summarize(events: TurnEvent[]): InteractionSummary {
|
|
|
131
141
|
sessions: new Set(events.map((e) => e.session)).size,
|
|
132
142
|
channels: tally(events.map((e) => e.channel ?? "unknown")),
|
|
133
143
|
agents: byAgent(events),
|
|
134
|
-
reactions: tally(events.flatMap((e) => (e.reaction ? [e
|
|
144
|
+
reactions: tally(events.flatMap((e) => (e.reaction ? [reactionName(e)] : []))),
|
|
145
|
+
readBeforeGoAhead: events.filter((e) => e.reaction && !approvalIsKnown(e)).length,
|
|
135
146
|
hints: tally(hinted.map((e) => e.mood || "back to usual")),
|
|
136
147
|
unlogged: events.filter((e) => e.mood === undefined).length,
|
|
137
148
|
usual: stats(replies.filter((r) => r.labels.length === 0)),
|
|
@@ -255,5 +266,10 @@ export function reportLines(summary: InteractionSummary, days: number): string[]
|
|
|
255
266
|
"",
|
|
256
267
|
`${summary.unlogged} older turns were logged before moods were, and are left out of the comparison.`
|
|
257
268
|
);
|
|
269
|
+
if (summary.readBeforeGoAhead)
|
|
270
|
+
lines.push(
|
|
271
|
+
"",
|
|
272
|
+
`${summary.readBeforeGoAhead} older replies were read before go-ahead was split from approval, and are left out of the approval figures.`
|
|
273
|
+
);
|
|
258
274
|
return lines;
|
|
259
275
|
}
|