portable-agent-layer 0.108.0 → 0.109.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/README.md +2 -1
- package/package.json +6 -2
- package/src/cli/correction.ts +115 -0
- package/src/cli/rule.ts +121 -14
- package/src/cli/tree.ts +2 -0
- package/src/hooks/handlers/rating.ts +21 -2
- package/src/hooks/handlers/update-check.ts +6 -1
- package/src/hooks/lib/adaptation-candidates.ts +41 -13
- package/src/hooks/lib/adaptation-rules.ts +138 -9
- package/src/hooks/lib/adaptation-steering.ts +7 -2
- package/src/hooks/lib/adaptation-turns.ts +43 -1
- package/src/hooks/lib/context.ts +2 -2
- package/src/hooks/lib/correction-backfill.ts +56 -0
- package/src/hooks/lib/correction-kind.ts +165 -0
- package/src/hooks/lib/correction-rates.ts +151 -0
- package/src/hooks/lib/models.ts +7 -3
- package/src/hooks/lib/opinion-gate.ts +73 -0
- package/src/hooks/lib/rule-drafter.ts +57 -24
- package/src/hooks/lib/semi-static.ts +2 -2
- package/src/tools/control-room/relationship.ts +43 -18
- package/src/tools/control-room/server.ts +12 -5
- package/src/tools/control-room/ui/dist/assets/{index-mFpXpiBj.js → index-D0pEd7Ln.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/lib/write.ts +3 -1
- package/src/tools/control-room/ui/screens/relationship.tsx +101 -29
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
rel="stylesheet"
|
|
11
11
|
href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600&family=Barlow+Condensed:wght@400;500;600&display=swap"
|
|
12
12
|
/>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-D0pEd7Ln.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-DeglMbfS.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* server's own error text, so a refusal reads the same wherever it surfaces.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { PageDecision } from "../../relationship";
|
|
7
|
+
|
|
6
8
|
export async function post(path: string, body: unknown): Promise<string | null> {
|
|
7
9
|
try {
|
|
8
10
|
const res = await fetch(path, {
|
|
@@ -46,7 +48,7 @@ export function setAutoUpdate(enabled: boolean) {
|
|
|
46
48
|
return post("/api/update", { enabled });
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
export function decideRule(id: string, decision:
|
|
51
|
+
export function decideRule(id: string, decision: PageDecision) {
|
|
50
52
|
return post("/api/rule", { id, decision });
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -3,7 +3,14 @@ import type {
|
|
|
3
3
|
AdaptationRule,
|
|
4
4
|
TriggerProof,
|
|
5
5
|
} from "../../../../hooks/lib/adaptation-rules";
|
|
6
|
-
import type {
|
|
6
|
+
import type { OpinionEffect } from "../../../../hooks/lib/correction-rates";
|
|
7
|
+
import type {
|
|
8
|
+
ActiveRule,
|
|
9
|
+
DraftRule,
|
|
10
|
+
PageDecision,
|
|
11
|
+
PipelineCandidate,
|
|
12
|
+
RelationshipView,
|
|
13
|
+
} from "../../relationship";
|
|
7
14
|
import { Badge } from "../components/badge";
|
|
8
15
|
import { Button } from "../components/button";
|
|
9
16
|
import { clock } from "../format";
|
|
@@ -33,9 +40,14 @@ function Facts({ rows }: { rows: [string, string | undefined][] }) {
|
|
|
33
40
|
);
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
function triggerLine(rule: AdaptationRule): string {
|
|
44
|
+
if (rule.delivery === "always") return "none, an opinion steers every session";
|
|
45
|
+
return rule.trigger ? `${rule.trigger.side} · /${rule.trigger.pattern}/i` : "none";
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
function ruleFacts(rule: AdaptationRule): [string, string | undefined][] {
|
|
37
49
|
return [
|
|
38
|
-
["trigger",
|
|
50
|
+
["trigger", triggerLine(rule)],
|
|
39
51
|
["steering", rule.steering],
|
|
40
52
|
["check", rule.check],
|
|
41
53
|
["proof", proofLine(rule.proof)],
|
|
@@ -56,43 +68,82 @@ function Evidence({ items }: { items: string[] }) {
|
|
|
56
68
|
function replacedTrigger(rule: AdaptationRule, active: ActiveRule[]): string | undefined {
|
|
57
69
|
if (!rule.widens) return undefined;
|
|
58
70
|
const target = active.find((r) => r.id === rule.widens);
|
|
59
|
-
if (!target) return `the trigger of rule ${rule.widens}`;
|
|
71
|
+
if (!target?.trigger) return `the trigger of rule ${rule.widens}`;
|
|
60
72
|
return `/${target.trigger.pattern}/i`;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
function
|
|
64
|
-
rule,
|
|
65
|
-
active,
|
|
66
|
-
onDecided,
|
|
67
|
-
}: {
|
|
68
|
-
rule: AdaptationRule;
|
|
69
|
-
active: ActiveRule[];
|
|
70
|
-
onDecided: () => void;
|
|
71
|
-
}) {
|
|
75
|
+
function useDecision(id: string, onDecided: () => void) {
|
|
72
76
|
const [error, setError] = useState<string | null>(null);
|
|
73
77
|
const [busy, setBusy] = useState(false);
|
|
74
|
-
const decide = (decision:
|
|
78
|
+
const decide = (decision: PageDecision) => {
|
|
75
79
|
setBusy(true);
|
|
76
|
-
void decideRule(
|
|
80
|
+
void decideRule(id, decision).then((failure) => {
|
|
77
81
|
setBusy(false);
|
|
78
82
|
setError(failure);
|
|
79
83
|
if (!failure) onDecided();
|
|
80
84
|
});
|
|
81
85
|
};
|
|
86
|
+
return { error, busy, decide };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Refusal({ error }: { error: string | null }) {
|
|
90
|
+
if (!error) return null;
|
|
91
|
+
return (
|
|
92
|
+
<p className="border-l-2 border-alarm bg-alarm/10 px-3 py-2 text-[12px] text-alarm">
|
|
93
|
+
{error}
|
|
94
|
+
</p>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function offeredDecisions(rule: AdaptationRule): [PageDecision, string][] {
|
|
99
|
+
if (rule.widens) return [["approved", "Approve"]];
|
|
100
|
+
const asOpinion: [PageDecision, string][] =
|
|
101
|
+
rule.kind && !rule.opinionGate ? [["opinion", "Keep as opinion"]] : [];
|
|
102
|
+
if (rule.offer === "opinion-only") return asOpinion;
|
|
103
|
+
return [["approved", "Approve as rule"], ...asOpinion];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function offerLine(rule: AdaptationRule): string | undefined {
|
|
107
|
+
if (rule.offer !== "opinion-only" || rule.opinionGate) return undefined;
|
|
108
|
+
const why = rule.trigger ? "the trigger did not prove" : "no trigger";
|
|
109
|
+
return `${why}; keep it as an opinion or deny it`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function Draft({
|
|
113
|
+
rule,
|
|
114
|
+
active,
|
|
115
|
+
onDecided,
|
|
116
|
+
}: {
|
|
117
|
+
rule: DraftRule;
|
|
118
|
+
active: ActiveRule[];
|
|
119
|
+
onDecided: () => void;
|
|
120
|
+
}) {
|
|
121
|
+
const { error, busy, decide } = useDecision(rule.id, onDecided);
|
|
82
122
|
return (
|
|
83
123
|
<div className="flex flex-col gap-3 border-t border-divider py-3 first:border-t-0">
|
|
84
124
|
<div className="font-heading text-[15px] font-semibold">{rule.when}</div>
|
|
85
|
-
<Facts
|
|
125
|
+
<Facts
|
|
126
|
+
rows={[
|
|
127
|
+
["replaces", replacedTrigger(rule, active)],
|
|
128
|
+
...ruleFacts(rule),
|
|
129
|
+
["kind", rule.kind],
|
|
130
|
+
["offer", offerLine(rule)],
|
|
131
|
+
["no opinion", rule.gateAdvice],
|
|
132
|
+
]}
|
|
133
|
+
/>
|
|
86
134
|
<Evidence items={rule.evidence} />
|
|
87
|
-
{error
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
<Refusal error={error} />
|
|
136
|
+
<div className="flex flex-wrap gap-3">
|
|
137
|
+
{offeredDecisions(rule).map(([decision, label], i) => (
|
|
138
|
+
<Button
|
|
139
|
+
key={decision}
|
|
140
|
+
variant={i === 0 ? "primary" : undefined}
|
|
141
|
+
disabled={busy}
|
|
142
|
+
onClick={() => decide(decision)}
|
|
143
|
+
>
|
|
144
|
+
{label}
|
|
145
|
+
</Button>
|
|
146
|
+
))}
|
|
96
147
|
<Button disabled={busy} onClick={() => decide("denied")}>
|
|
97
148
|
Deny
|
|
98
149
|
</Button>
|
|
@@ -101,18 +152,39 @@ function Draft({
|
|
|
101
152
|
);
|
|
102
153
|
}
|
|
103
154
|
|
|
104
|
-
function
|
|
155
|
+
function firingLine(rule: ActiveRule): string {
|
|
105
156
|
const { fired, sentBack, judged, correctedAfter } = rule.effect;
|
|
106
157
|
if (fired === 0) return "has not fired yet";
|
|
107
|
-
const back = rule.trigger
|
|
158
|
+
const back = rule.trigger?.side === "reply" ? `, sent back ${sentBack}` : "";
|
|
108
159
|
return `fired ${fired}${back} · corrected after ${correctedAfter} of ${judged} judged`;
|
|
109
160
|
}
|
|
110
161
|
|
|
111
|
-
function
|
|
162
|
+
function windowsLine({ before, after }: Pick<OpinionEffect, "before" | "after">): string {
|
|
163
|
+
return `${before.per100.toFixed(1)}/100 before (${before.count} in ${before.turns}) → ${after.per100.toFixed(1)}/100 after (${after.count} in ${after.turns})`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function effectRows(rule: ActiveRule): [string, string | undefined][] {
|
|
167
|
+
const opinion = rule.opinionEffect;
|
|
168
|
+
if (!opinion) return [["effect", firingLine(rule)]];
|
|
169
|
+
return [
|
|
170
|
+
["kind", opinion.kind],
|
|
171
|
+
["effect", `${windowsLine(opinion)} · ${opinion.verdict}`],
|
|
172
|
+
["other kinds", windowsLine(opinion.control)],
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function Active({ rule, onDecided }: { rule: ActiveRule; onDecided: () => void }) {
|
|
177
|
+
const { error, busy, decide } = useDecision(rule.id, onDecided);
|
|
112
178
|
return (
|
|
113
179
|
<div className="flex flex-col gap-2 border-t border-divider py-3 first:border-t-0">
|
|
114
180
|
<div className="font-heading text-[15px] font-semibold">{rule.when}</div>
|
|
115
|
-
<Facts rows={[...ruleFacts(rule),
|
|
181
|
+
<Facts rows={[...ruleFacts(rule), ...effectRows(rule)]} />
|
|
182
|
+
<Refusal error={error} />
|
|
183
|
+
<div>
|
|
184
|
+
<Button disabled={busy} onClick={() => decide("retired")}>
|
|
185
|
+
Retire
|
|
186
|
+
</Button>
|
|
187
|
+
</div>
|
|
116
188
|
</div>
|
|
117
189
|
);
|
|
118
190
|
}
|
|
@@ -230,7 +302,7 @@ export function Relationship() {
|
|
|
230
302
|
{active.length === 0 ? (
|
|
231
303
|
<Empty>No rule approved yet.</Empty>
|
|
232
304
|
) : (
|
|
233
|
-
active.map((rule) => <Active key={rule.id} rule={rule} />)
|
|
305
|
+
active.map((rule) => <Active key={rule.id} rule={rule} onDecided={reload} />)
|
|
234
306
|
)}
|
|
235
307
|
</Panel>
|
|
236
308
|
<LastDays view={view.data} />
|