dsh-llm-verifier 0.6.0 → 0.6.2
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 +3 -0
- package/lib/{auto-DAMeKRkK.js → auto-caXDU2pw.js} +19 -1
- package/lib/{auto-DAMeKRkK.js.map → auto-caXDU2pw.js.map} +1 -1
- package/lib/client.js +493 -331
- package/lib/client.js.map +1 -1
- package/lib/index.js +201 -52
- package/lib/index.js.map +1 -1
- package/lib/replay.js +1 -1
- package/lib/types/client-i18n.d.ts +36 -11
- package/lib/types/client-i18n.d.ts.map +1 -1
- package/lib/types/client-i18n.js +50 -9
- package/lib/types/client-i18n.js.map +1 -1
- package/lib/types/client.d.ts +17 -7
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +165 -44
- package/lib/types/client.js.map +1 -1
- package/lib/types/decisions.d.ts +8 -0
- package/lib/types/decisions.d.ts.map +1 -1
- package/lib/types/decisions.js +13 -1
- package/lib/types/decisions.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +47 -31
- package/lib/types/index.js.map +1 -1
- package/lib/types/process-selection.d.ts +27 -5
- package/lib/types/process-selection.d.ts.map +1 -1
- package/lib/types/process-selection.js +17 -8
- package/lib/types/process-selection.js.map +1 -1
- package/lib/types/router.d.ts +22 -0
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +20 -0
- package/lib/types/router.js.map +1 -1
- package/lib/types/statistics.d.ts +8 -0
- package/lib/types/statistics.d.ts.map +1 -1
- package/lib/types/statistics.js +1 -1
- package/lib/types/statistics.js.map +1 -1
- package/lib/types/verifier-activity.d.ts +189 -0
- package/lib/types/verifier-activity.d.ts.map +1 -0
- package/lib/types/verifier-activity.js +223 -0
- package/lib/types/verifier-activity.js.map +1 -0
- package/package.json +1 -1
- package/src/client-i18n.ts +55 -16
- package/src/client.test.ts +101 -16
- package/src/client.tsx +238 -103
- package/src/decisions.test.ts +16 -0
- package/src/decisions.ts +22 -1
- package/src/index.test.ts +32 -0
- package/src/index.ts +48 -33
- package/src/process-selection.test.ts +13 -0
- package/src/process-selection.ts +39 -14
- package/src/router.test.ts +46 -0
- package/src/router.ts +31 -0
- package/src/statistics.test.ts +16 -0
- package/src/statistics.ts +9 -1
- package/src/verifier-activity.test.ts +164 -0
- package/src/verifier-activity.ts +321 -0
- package/lib/types/process-activity.d.ts +0 -109
- package/lib/types/process-activity.d.ts.map +0 -1
- package/lib/types/process-activity.js +0 -135
- package/lib/types/process-activity.js.map +0 -1
- package/src/process-activity.test.ts +0 -91
- package/src/process-activity.ts +0 -177
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory view of the verifier's in-flight cycles, for the chat chip.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* Every automatic stage runs judge calls while the chat shows nothing about it. Two of them are
|
|
7
|
+
* silent windows rather than "no output at all":
|
|
8
|
+
*
|
|
9
|
+
* - P06 buffers the main reply, so while a cycle runs there is no streaming text whatsoever —
|
|
10
|
+
* indistinguishable from a hang;
|
|
11
|
+
* - the routed reviews (compare/select/track) and the final acceptance run inside
|
|
12
|
+
* `agent/turn-stopping`, where the reply is already on screen but the turn stays open for as long
|
|
13
|
+
* as the judges take (a bounded but real pause, and a passing acceptance says nothing at all).
|
|
14
|
+
*
|
|
15
|
+
* The harness renders the host's own inline indicator ("Compacting context…") from a *session event*
|
|
16
|
+
* pair, and that route is closed to an out-of-repo plugin: the persistence read path
|
|
17
|
+
* (`validateStoredEvents` in `@deepseek-ai/dsh-session-persistence`) refuses to interpret a log
|
|
18
|
+
* containing an event type outside the repository-generated `KNOWN_SESSION_EVENT_TYPES` unless the
|
|
19
|
+
* stored envelope carries `ignorable: true`, and `Session.append` cannot set that marker — only a
|
|
20
|
+
* seed/restore envelope can. Appending our own type would therefore make the topic unloadable.
|
|
21
|
+
*
|
|
22
|
+
* So the indicator is deliberately host-only state: the router (the one place that knows a cycle was
|
|
23
|
+
* granted) and the P06 selector publish what they are doing here, the plugin's existing RPC answers
|
|
24
|
+
* a read of it, and the client polls that read only while a turn runs. Nothing here is durable (a
|
|
25
|
+
* reload shows nothing, which is correct: the cycle it described is gone) and nothing here touches
|
|
26
|
+
* the model history, the session log, the statistics or any verdict.
|
|
27
|
+
*
|
|
28
|
+
* Pure bookkeeping: no I/O, no clock of its own (callers pass `now`), so it is directly testable.
|
|
29
|
+
*/
|
|
30
|
+
import type { Reservation, RouterCycleObserver } from './router.ts';
|
|
31
|
+
/** Which part of the plugin owns a cycle. */
|
|
32
|
+
export type ActivityStage = 'process' | 'route' | 'final';
|
|
33
|
+
/** What a cycle is doing right now, as far as the UI can tell it apart. */
|
|
34
|
+
export type ActivityPhase = 'generating' | 'comparing' | 'classifying' | 'reviewing' | 'accepting';
|
|
35
|
+
/** The routed decision a `route` cycle resolved to, once classification promoted it. */
|
|
36
|
+
export type ActivityDestination = 'compare' | 'select' | 'track';
|
|
37
|
+
/** How a finished cycle resolved, from the user's point of view. */
|
|
38
|
+
export type ActivityOutcome = 'replaced' | 'kept' | 'same' | 'failed' | 'accepted' | 'rejected';
|
|
39
|
+
/** One cycle in flight. */
|
|
40
|
+
export interface ActivityActive {
|
|
41
|
+
cycleId: string;
|
|
42
|
+
stage: ActivityStage;
|
|
43
|
+
phase: ActivityPhase;
|
|
44
|
+
/** Routing target of a `route` cycle; absent while it is still classifying and for the others. */
|
|
45
|
+
destination?: ActivityDestination;
|
|
46
|
+
/** Candidates in the P06 cycle, the original reply included (2..4). */
|
|
47
|
+
candidates?: number;
|
|
48
|
+
/** Model calls this cycle reserved, for the copy that quotes a size. */
|
|
49
|
+
expectedCalls?: number;
|
|
50
|
+
/** `provider/model` of P06's second generator, when `autoProcessAlternativeModel` is set. */
|
|
51
|
+
alternativeModel?: string;
|
|
52
|
+
startedAt: number;
|
|
53
|
+
}
|
|
54
|
+
/** One cycle that just finished; kept only long enough for the chip to explain the pause. */
|
|
55
|
+
export interface ActivitySettled {
|
|
56
|
+
cycleId: string;
|
|
57
|
+
stage: ActivityStage;
|
|
58
|
+
outcome: ActivityOutcome;
|
|
59
|
+
candidates?: number;
|
|
60
|
+
/** Wall clock the cycle settled at. */
|
|
61
|
+
at: number;
|
|
62
|
+
}
|
|
63
|
+
/** What one session currently has to show. Both fields absent means "nothing to say". */
|
|
64
|
+
export interface ActivityView {
|
|
65
|
+
active?: ActivityActive;
|
|
66
|
+
settled?: ActivitySettled;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* How long a settled cycle keeps explaining itself.
|
|
70
|
+
*
|
|
71
|
+
* Long enough to be read after the reply lands (the chat scrolls as soon as the winner replays),
|
|
72
|
+
* short enough that a stale line never becomes part of the transcript.
|
|
73
|
+
*/
|
|
74
|
+
export declare const ACTIVITY_SETTLED_TTL_MS = 20000;
|
|
75
|
+
/**
|
|
76
|
+
* Safety bound on an in-flight record.
|
|
77
|
+
*
|
|
78
|
+
* Every cycle is bounded by the configured phase timeout and settles on every path we know of, so
|
|
79
|
+
* this only ever fires for a cycle that leaked (a bug, or a host that never calls back). Without it
|
|
80
|
+
* a leaked record would pin the chip in "working…" forever, because the client keeps polling while
|
|
81
|
+
* it has something to render.
|
|
82
|
+
*/
|
|
83
|
+
export declare const ACTIVITY_ACTIVE_TTL_MS = 600000;
|
|
84
|
+
/**
|
|
85
|
+
* Map one finished P06 cycle onto the outcome the chip distinguishes.
|
|
86
|
+
*
|
|
87
|
+
* `replayed` is the host-facing fact ("which stream did the host actually receive") and wins over
|
|
88
|
+
* the outcome label: a cycle recorded as `candidate-selected` whose winner was withheld before the
|
|
89
|
+
* first chunk is corrected to `replayed: 'original'` and must read as "kept", not as a replacement.
|
|
90
|
+
* @param outcome - the durable outcome string the cycle was recorded with.
|
|
91
|
+
* @param replayed - which stream the host actually received.
|
|
92
|
+
* @returns The chip-level outcome.
|
|
93
|
+
*/
|
|
94
|
+
export declare function classifyProcessOutcome(outcome: string, replayed: 'original' | 'candidate' | 'none'): ActivityOutcome;
|
|
95
|
+
/**
|
|
96
|
+
* Map one granted reservation onto the chip's active record.
|
|
97
|
+
*
|
|
98
|
+
* `process` is deliberately NOT mapped: P06 publishes its own richer record (candidate count,
|
|
99
|
+
* generation vs comparison), and two writers for one cycle id would race. `plan_review` and
|
|
100
|
+
* `team_task` are not announced either — the plan gate already shows a pending tool row in the
|
|
101
|
+
* chat, so a chip line would only repeat it.
|
|
102
|
+
* @param reservation - the reservation the router just granted.
|
|
103
|
+
* @param startedAt - wall clock the cycle began at.
|
|
104
|
+
* @returns The active record, or undefined for a phase the chip stays silent about.
|
|
105
|
+
*/
|
|
106
|
+
export declare function reservationActivity(reservation: Pick<Reservation, 'id' | 'phase' | 'expectedCalls'>, startedAt: number): ActivityActive | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Map a promotion onto the update the chip needs.
|
|
109
|
+
*
|
|
110
|
+
* A classification cycle that resolved to a decision stays the SAME reservation (that is what
|
|
111
|
+
* promotion is for), so the chip must be moved from "classifying" to "reviewing" instead of leaving
|
|
112
|
+
* a second cycle behind.
|
|
113
|
+
* @param reservation - the promoted reservation, already carrying its execution phase.
|
|
114
|
+
* @returns The patch, or undefined when the phase is not one the chip announces.
|
|
115
|
+
*/
|
|
116
|
+
export declare function reservationPromotion(reservation: Pick<Reservation, 'phase' | 'expectedCalls'>): Pick<ActivityActive, 'phase' | 'expectedCalls'> & {
|
|
117
|
+
destination?: ActivityDestination;
|
|
118
|
+
} | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Map a router settlement onto the chip's settled row.
|
|
121
|
+
*
|
|
122
|
+
* A routed verdict is steered into the chat by the caller right after the cycle settles, so a second
|
|
123
|
+
* transient line would only repeat it; the final gate is the opposite (a PASSING acceptance steers
|
|
124
|
+
* nothing), and that is exactly the case worth showing. `undefined` means "clear the active row and
|
|
125
|
+
* leave nothing behind".
|
|
126
|
+
* @param reservation - the reservation being settled.
|
|
127
|
+
* @param outcome - whether the router committed or failed it.
|
|
128
|
+
* @returns The stage and outcome to keep, or undefined to clear silently.
|
|
129
|
+
*/
|
|
130
|
+
export declare function reservationSettled(reservation: Pick<Reservation, 'id' | 'phase'>, outcome: 'committed' | 'failed'): {
|
|
131
|
+
stage: ActivityStage;
|
|
132
|
+
outcome: ActivityOutcome;
|
|
133
|
+
} | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Build the router's advisory observer over one activity table.
|
|
136
|
+
*
|
|
137
|
+
* The router supplies the reservation (it is the only component that knows a cycle was granted and
|
|
138
|
+
* when it settled); this module owns what the chip says about it.
|
|
139
|
+
* @param activities - the table the plugin's RPC reads.
|
|
140
|
+
* @param now - wall clock used for the records and their TTLs.
|
|
141
|
+
* @returns The observer to hand to `new AutoVerifierRouter(...)`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function createActivityObserver(activities: VerifierActivities, now?: () => number): RouterCycleObserver;
|
|
144
|
+
/**
|
|
145
|
+
* Per-session activity table.
|
|
146
|
+
*
|
|
147
|
+
* One active cycle per session, because the router owns one in-flight reservation per agent at a
|
|
148
|
+
* time; a settled cycle replaces the previous settled one (the chip is a status line, not a history
|
|
149
|
+
* — the statistics sidecar is the history).
|
|
150
|
+
*/
|
|
151
|
+
export declare class VerifierActivities {
|
|
152
|
+
private readonly active;
|
|
153
|
+
private readonly settled;
|
|
154
|
+
/** Publish one cycle as in flight. */
|
|
155
|
+
begin(sessionId: string, activity: ActivityActive): void;
|
|
156
|
+
/**
|
|
157
|
+
* Move one in-flight cycle to a later phase.
|
|
158
|
+
*
|
|
159
|
+
* Guarded by cycle id: a cycle that already settled (or was replaced by a newer one) must not be
|
|
160
|
+
* moved back into "in flight" by a late async step.
|
|
161
|
+
* @returns whether the exact cycle was the active one.
|
|
162
|
+
*/
|
|
163
|
+
update(sessionId: string, cycleId: string, patch: Partial<Pick<ActivityActive, 'phase' | 'destination' | 'candidates' | 'expectedCalls'>>): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Retire one cycle.
|
|
166
|
+
*
|
|
167
|
+
* A cycle nobody observed (the purchase itself failed, so `begin` never ran) leaves nothing
|
|
168
|
+
* behind: the chip must not claim a cycle that never started. A cycle that WAS observed stays
|
|
169
|
+
* correctable while its own settled record is the current one, which is what P06's late
|
|
170
|
+
* "the winner was not delivered" correction needs.
|
|
171
|
+
* @param sessionId - the session the cycle belongs to.
|
|
172
|
+
* @param cycleId - the cycle to retire.
|
|
173
|
+
* @param outcome - the settled row to keep, or undefined to clear without leaving one.
|
|
174
|
+
* @param now - wall clock used for the settled record.
|
|
175
|
+
* @returns whether the cycle was known and retired.
|
|
176
|
+
*/
|
|
177
|
+
finish(sessionId: string, cycleId: string, outcome: ActivityOutcome | undefined, now: number): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* What one session has to show right now, with expired records pruned in place.
|
|
180
|
+
* @param sessionId - the session to read.
|
|
181
|
+
* @param now - wall clock used for both TTLs.
|
|
182
|
+
*/
|
|
183
|
+
read(sessionId: string, now: number): ActivityView;
|
|
184
|
+
/** Forget one session entirely (settings changed, task replaced, agent disposed). */
|
|
185
|
+
clear(sessionId: string): void;
|
|
186
|
+
/** Forget every session (settings change, shutdown). */
|
|
187
|
+
clearAll(): void;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=verifier-activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier-activity.d.ts","sourceRoot":"","sources":["../../src/verifier-activity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a settled cycle keeps explaining itself.
|
|
3
|
+
*
|
|
4
|
+
* Long enough to be read after the reply lands (the chat scrolls as soon as the winner replays),
|
|
5
|
+
* short enough that a stale line never becomes part of the transcript.
|
|
6
|
+
*/
|
|
7
|
+
export const ACTIVITY_SETTLED_TTL_MS = 20_000;
|
|
8
|
+
/**
|
|
9
|
+
* Safety bound on an in-flight record.
|
|
10
|
+
*
|
|
11
|
+
* Every cycle is bounded by the configured phase timeout and settles on every path we know of, so
|
|
12
|
+
* this only ever fires for a cycle that leaked (a bug, or a host that never calls back). Without it
|
|
13
|
+
* a leaked record would pin the chip in "working…" forever, because the client keeps polling while
|
|
14
|
+
* it has something to render.
|
|
15
|
+
*/
|
|
16
|
+
export const ACTIVITY_ACTIVE_TTL_MS = 600_000;
|
|
17
|
+
/**
|
|
18
|
+
* Map one finished P06 cycle onto the outcome the chip distinguishes.
|
|
19
|
+
*
|
|
20
|
+
* `replayed` is the host-facing fact ("which stream did the host actually receive") and wins over
|
|
21
|
+
* the outcome label: a cycle recorded as `candidate-selected` whose winner was withheld before the
|
|
22
|
+
* first chunk is corrected to `replayed: 'original'` and must read as "kept", not as a replacement.
|
|
23
|
+
* @param outcome - the durable outcome string the cycle was recorded with.
|
|
24
|
+
* @param replayed - which stream the host actually received.
|
|
25
|
+
* @returns The chip-level outcome.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyProcessOutcome(outcome, replayed) {
|
|
28
|
+
if (replayed === 'candidate')
|
|
29
|
+
return 'replaced';
|
|
30
|
+
if (outcome === 'identical-candidate')
|
|
31
|
+
return 'same';
|
|
32
|
+
if (outcome === 'tie' || outcome === 'original-selected')
|
|
33
|
+
return 'kept';
|
|
34
|
+
if (outcome.startsWith('candidate-not-delivered'))
|
|
35
|
+
return 'kept';
|
|
36
|
+
return 'failed';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map one granted reservation onto the chip's active record.
|
|
40
|
+
*
|
|
41
|
+
* `process` is deliberately NOT mapped: P06 publishes its own richer record (candidate count,
|
|
42
|
+
* generation vs comparison), and two writers for one cycle id would race. `plan_review` and
|
|
43
|
+
* `team_task` are not announced either — the plan gate already shows a pending tool row in the
|
|
44
|
+
* chat, so a chip line would only repeat it.
|
|
45
|
+
* @param reservation - the reservation the router just granted.
|
|
46
|
+
* @param startedAt - wall clock the cycle began at.
|
|
47
|
+
* @returns The active record, or undefined for a phase the chip stays silent about.
|
|
48
|
+
*/
|
|
49
|
+
export function reservationActivity(reservation, startedAt) {
|
|
50
|
+
switch (reservation.phase) {
|
|
51
|
+
case 'semantic':
|
|
52
|
+
return { cycleId: reservation.id, stage: 'route', phase: 'classifying', expectedCalls: reservation.expectedCalls, startedAt };
|
|
53
|
+
case 'compare':
|
|
54
|
+
case 'select':
|
|
55
|
+
case 'track':
|
|
56
|
+
return { cycleId: reservation.id, stage: 'route', phase: 'reviewing', destination: reservation.phase, expectedCalls: reservation.expectedCalls, startedAt };
|
|
57
|
+
case 'final':
|
|
58
|
+
return { cycleId: reservation.id, stage: 'final', phase: 'accepting', expectedCalls: reservation.expectedCalls, startedAt };
|
|
59
|
+
default:
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Map a promotion onto the update the chip needs.
|
|
65
|
+
*
|
|
66
|
+
* A classification cycle that resolved to a decision stays the SAME reservation (that is what
|
|
67
|
+
* promotion is for), so the chip must be moved from "classifying" to "reviewing" instead of leaving
|
|
68
|
+
* a second cycle behind.
|
|
69
|
+
* @param reservation - the promoted reservation, already carrying its execution phase.
|
|
70
|
+
* @returns The patch, or undefined when the phase is not one the chip announces.
|
|
71
|
+
*/
|
|
72
|
+
export function reservationPromotion(reservation) {
|
|
73
|
+
switch (reservation.phase) {
|
|
74
|
+
case 'compare':
|
|
75
|
+
case 'select':
|
|
76
|
+
case 'track':
|
|
77
|
+
return { phase: 'reviewing', destination: reservation.phase, expectedCalls: reservation.expectedCalls };
|
|
78
|
+
default:
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Map a router settlement onto the chip's settled row.
|
|
84
|
+
*
|
|
85
|
+
* A routed verdict is steered into the chat by the caller right after the cycle settles, so a second
|
|
86
|
+
* transient line would only repeat it; the final gate is the opposite (a PASSING acceptance steers
|
|
87
|
+
* nothing), and that is exactly the case worth showing. `undefined` means "clear the active row and
|
|
88
|
+
* leave nothing behind".
|
|
89
|
+
* @param reservation - the reservation being settled.
|
|
90
|
+
* @param outcome - whether the router committed or failed it.
|
|
91
|
+
* @returns The stage and outcome to keep, or undefined to clear silently.
|
|
92
|
+
*/
|
|
93
|
+
export function reservationSettled(reservation, outcome) {
|
|
94
|
+
return reservation.phase === 'final'
|
|
95
|
+
? { stage: 'final', outcome: outcome === 'committed' ? 'accepted' : 'rejected' }
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build the router's advisory observer over one activity table.
|
|
100
|
+
*
|
|
101
|
+
* The router supplies the reservation (it is the only component that knows a cycle was granted and
|
|
102
|
+
* when it settled); this module owns what the chip says about it.
|
|
103
|
+
* @param activities - the table the plugin's RPC reads.
|
|
104
|
+
* @param now - wall clock used for the records and their TTLs.
|
|
105
|
+
* @returns The observer to hand to `new AutoVerifierRouter(...)`.
|
|
106
|
+
*/
|
|
107
|
+
export function createActivityObserver(activities, now = Date.now) {
|
|
108
|
+
const key = (agent) => String(agent.id);
|
|
109
|
+
return {
|
|
110
|
+
begin: (agent, reservation) => {
|
|
111
|
+
const activity = reservationActivity(reservation, now());
|
|
112
|
+
if (activity !== undefined)
|
|
113
|
+
activities.begin(key(agent), activity);
|
|
114
|
+
},
|
|
115
|
+
promoted: (agent, reservation) => {
|
|
116
|
+
const patch = reservationPromotion(reservation);
|
|
117
|
+
if (patch !== undefined)
|
|
118
|
+
activities.update(key(agent), reservation.id, patch);
|
|
119
|
+
},
|
|
120
|
+
settled: (agent, reservation, outcome) => {
|
|
121
|
+
const settled = reservationSettled(reservation, outcome);
|
|
122
|
+
activities.finish(key(agent), reservation.id, settled?.outcome, now());
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Per-session activity table.
|
|
128
|
+
*
|
|
129
|
+
* One active cycle per session, because the router owns one in-flight reservation per agent at a
|
|
130
|
+
* time; a settled cycle replaces the previous settled one (the chip is a status line, not a history
|
|
131
|
+
* — the statistics sidecar is the history).
|
|
132
|
+
*/
|
|
133
|
+
export class VerifierActivities {
|
|
134
|
+
active = new Map();
|
|
135
|
+
settled = new Map();
|
|
136
|
+
/** Publish one cycle as in flight. */
|
|
137
|
+
begin(sessionId, activity) {
|
|
138
|
+
this.active.set(sessionId, activity);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Move one in-flight cycle to a later phase.
|
|
142
|
+
*
|
|
143
|
+
* Guarded by cycle id: a cycle that already settled (or was replaced by a newer one) must not be
|
|
144
|
+
* moved back into "in flight" by a late async step.
|
|
145
|
+
* @returns whether the exact cycle was the active one.
|
|
146
|
+
*/
|
|
147
|
+
update(sessionId, cycleId, patch) {
|
|
148
|
+
const current = this.active.get(sessionId);
|
|
149
|
+
if (current === undefined || current.cycleId !== cycleId)
|
|
150
|
+
return false;
|
|
151
|
+
this.active.set(sessionId, { ...current, ...patch });
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Retire one cycle.
|
|
156
|
+
*
|
|
157
|
+
* A cycle nobody observed (the purchase itself failed, so `begin` never ran) leaves nothing
|
|
158
|
+
* behind: the chip must not claim a cycle that never started. A cycle that WAS observed stays
|
|
159
|
+
* correctable while its own settled record is the current one, which is what P06's late
|
|
160
|
+
* "the winner was not delivered" correction needs.
|
|
161
|
+
* @param sessionId - the session the cycle belongs to.
|
|
162
|
+
* @param cycleId - the cycle to retire.
|
|
163
|
+
* @param outcome - the settled row to keep, or undefined to clear without leaving one.
|
|
164
|
+
* @param now - wall clock used for the settled record.
|
|
165
|
+
* @returns whether the cycle was known and retired.
|
|
166
|
+
*/
|
|
167
|
+
finish(sessionId, cycleId, outcome, now) {
|
|
168
|
+
const active = this.active.get(sessionId);
|
|
169
|
+
const settled = this.settled.get(sessionId);
|
|
170
|
+
const known = active?.cycleId === cycleId
|
|
171
|
+
? active
|
|
172
|
+
: settled?.cycleId === cycleId
|
|
173
|
+
? settled
|
|
174
|
+
: undefined;
|
|
175
|
+
if (known === undefined)
|
|
176
|
+
return false;
|
|
177
|
+
if (active?.cycleId === cycleId)
|
|
178
|
+
this.active.delete(sessionId);
|
|
179
|
+
if (outcome === undefined) {
|
|
180
|
+
if (settled?.cycleId === cycleId)
|
|
181
|
+
this.settled.delete(sessionId);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
this.settled.set(sessionId, {
|
|
185
|
+
cycleId,
|
|
186
|
+
stage: known.stage,
|
|
187
|
+
outcome,
|
|
188
|
+
...(known.candidates === undefined ? {} : { candidates: known.candidates }),
|
|
189
|
+
at: now,
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* What one session has to show right now, with expired records pruned in place.
|
|
195
|
+
* @param sessionId - the session to read.
|
|
196
|
+
* @param now - wall clock used for both TTLs.
|
|
197
|
+
*/
|
|
198
|
+
read(sessionId, now) {
|
|
199
|
+
const active = this.active.get(sessionId);
|
|
200
|
+
if (active !== undefined && now - active.startedAt >= ACTIVITY_ACTIVE_TTL_MS)
|
|
201
|
+
this.active.delete(sessionId);
|
|
202
|
+
const settled = this.settled.get(sessionId);
|
|
203
|
+
if (settled !== undefined && now - settled.at >= ACTIVITY_SETTLED_TTL_MS)
|
|
204
|
+
this.settled.delete(sessionId);
|
|
205
|
+
const currentActive = this.active.get(sessionId);
|
|
206
|
+
const currentSettled = this.settled.get(sessionId);
|
|
207
|
+
return {
|
|
208
|
+
...(currentActive === undefined ? {} : { active: currentActive }),
|
|
209
|
+
...(currentSettled === undefined ? {} : { settled: currentSettled }),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** Forget one session entirely (settings changed, task replaced, agent disposed). */
|
|
213
|
+
clear(sessionId) {
|
|
214
|
+
this.active.delete(sessionId);
|
|
215
|
+
this.settled.delete(sessionId);
|
|
216
|
+
}
|
|
217
|
+
/** Forget every session (settings change, shutdown). */
|
|
218
|
+
clearAll() {
|
|
219
|
+
this.active.clear();
|
|
220
|
+
this.settled.clear();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=verifier-activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier-activity.js","sourceRoot":"","sources":["../../src/verifier-activity.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client-i18n.ts
CHANGED
|
@@ -228,6 +228,7 @@ export const zh = {
|
|
|
228
228
|
'recent.decisionPrompt': '提示词',
|
|
229
229
|
'recent.decisionOutput': '原始回答',
|
|
230
230
|
'recent.details': '查看详情',
|
|
231
|
+
'recent.detailsHide': '收起详情',
|
|
231
232
|
'recent.detail.criterion': '判据',
|
|
232
233
|
'recent.detail.score': '分数',
|
|
233
234
|
'recent.detail.threshold': '阈值',
|
|
@@ -256,6 +257,15 @@ export const zh = {
|
|
|
256
257
|
'models.tokens': '{tokens} Token',
|
|
257
258
|
'models.empty': '暂无模型调用',
|
|
258
259
|
|
|
260
|
+
// Chat chip for the automatic stages that give no other sign of life
|
|
261
|
+
'activity.route.classifying': '正在识别需要独立复核的对象…',
|
|
262
|
+
'activity.route.compare': '正在比较候选方案(约 {n} 次调用)…',
|
|
263
|
+
'activity.route.select': '正在为多个候选排名(约 {n} 次调用)…',
|
|
264
|
+
'activity.route.track': '正在复核任务进度(约 {n} 次调用)…',
|
|
265
|
+
'activity.final.accepting': '正在最终验收(约 {n} 次调用)…',
|
|
266
|
+
'activity.final.accepted': '最终验收通过',
|
|
267
|
+
'activity.final.rejected': '最终验收未通过',
|
|
268
|
+
|
|
259
269
|
// Process selection (P06) chat chip: the reply is being buffered, so this is the only sign of life
|
|
260
270
|
'process.generating': '正在过程选优:生成 {n} 份候选…',
|
|
261
271
|
'process.comparing': '正在过程选优:裁判比较 {n} 份候选…',
|
|
@@ -563,6 +573,7 @@ export const en: I18nDict = {
|
|
|
563
573
|
'recent.decisionPrompt': 'Prompt',
|
|
564
574
|
'recent.decisionOutput': 'Raw answer',
|
|
565
575
|
'recent.details': 'Details',
|
|
576
|
+
'recent.detailsHide': 'Hide details',
|
|
566
577
|
'recent.detail.criterion': 'Criterion',
|
|
567
578
|
'recent.detail.score': 'Score',
|
|
568
579
|
'recent.detail.threshold': 'Threshold',
|
|
@@ -591,6 +602,15 @@ export const en: I18nDict = {
|
|
|
591
602
|
'models.tokens': '{tokens} Tokens',
|
|
592
603
|
'models.empty': 'No model calls',
|
|
593
604
|
|
|
605
|
+
// Chat chip for the automatic stages that give no other sign of life
|
|
606
|
+
'activity.route.classifying': 'Identifying what needs independent review…',
|
|
607
|
+
'activity.route.compare': 'Comparing the candidate replies (about {n} calls)…',
|
|
608
|
+
'activity.route.select': 'Ranking the candidate set (about {n} calls)…',
|
|
609
|
+
'activity.route.track': 'Reviewing task progress (about {n} calls)…',
|
|
610
|
+
'activity.final.accepting': 'Running the final acceptance (about {n} calls)…',
|
|
611
|
+
'activity.final.accepted': 'Final acceptance passed',
|
|
612
|
+
'activity.final.rejected': 'Final acceptance did not pass',
|
|
613
|
+
|
|
594
614
|
// Process selection (P06) chat chip: the reply is being buffered, so this is the only sign of life
|
|
595
615
|
'process.generating': 'Selecting the best reply: generating {n} candidates…',
|
|
596
616
|
'process.comparing': 'Selecting the best reply: judging {n} candidates…',
|
|
@@ -694,40 +714,59 @@ export function tFormat(template: string, params?: Record<string, string | numbe
|
|
|
694
714
|
return template.replace(/\{(\w+)\}/g, (match, key) => (key in params ? String(params[key]) : match))
|
|
695
715
|
}
|
|
696
716
|
|
|
697
|
-
/** The
|
|
698
|
-
export interface
|
|
699
|
-
active?: { phase?: unknown; candidates?: unknown;
|
|
700
|
-
settled?: {
|
|
717
|
+
/** The chat chip's wire shape (a subset of the server's `ActivityView`). */
|
|
718
|
+
export interface ActivityChipView {
|
|
719
|
+
active?: { stage?: unknown; phase?: unknown; destination?: unknown; candidates?: unknown; expectedCalls?: unknown }
|
|
720
|
+
settled?: { stage?: unknown; outcome?: unknown }
|
|
701
721
|
}
|
|
702
722
|
|
|
703
723
|
/** How the chip renders: a busy accent, a settled confirmation, or a failure note. */
|
|
704
|
-
export type
|
|
724
|
+
export type ActivityTone = 'busy' | 'ok' | 'error'
|
|
725
|
+
|
|
726
|
+
/** A count that is safe to print, falling back to the given minimum for a missing/garbled value. */
|
|
727
|
+
function activityCount(value: unknown, minimum: number): number {
|
|
728
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(minimum, Math.floor(value)) : minimum
|
|
729
|
+
}
|
|
705
730
|
|
|
706
731
|
/**
|
|
707
|
-
* Render the
|
|
732
|
+
* Render the chat chip, or nothing when there is nothing to say.
|
|
708
733
|
*
|
|
709
734
|
* Pure on purpose (no React, no locale detection): both the chip and its regression tests read the
|
|
710
|
-
* same mapping.
|
|
711
|
-
* does not know must still show that work is happening, not disappear.
|
|
735
|
+
* same mapping. Unknown enum values degrade to the least surprising copy — a newer host describing a
|
|
736
|
+
* phase or a stage this build does not know must still show that work is happening, not disappear.
|
|
712
737
|
* @param view - the server's activity view, or nothing.
|
|
713
738
|
* @param t - the active dictionary.
|
|
714
739
|
* @returns The tone and copy, or null to render no row at all.
|
|
715
740
|
*/
|
|
716
|
-
export function
|
|
717
|
-
view:
|
|
741
|
+
export function verifierActivityText(
|
|
742
|
+
view: ActivityChipView | null | undefined,
|
|
718
743
|
t: I18nDict,
|
|
719
|
-
): { tone:
|
|
744
|
+
): { tone: ActivityTone; text: string } | null {
|
|
720
745
|
const active = view?.active
|
|
721
746
|
if (active !== undefined && active !== null) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
747
|
+
if (active.stage === 'route') {
|
|
748
|
+
if (active.phase !== 'reviewing') return { tone: 'busy', text: t['activity.route.classifying'] }
|
|
749
|
+
const calls = activityCount(active.expectedCalls, 1)
|
|
750
|
+
if (active.destination === 'select') return { tone: 'busy', text: tFormat(t['activity.route.select'], { n: calls }) }
|
|
751
|
+
if (active.destination === 'track') return { tone: 'busy', text: tFormat(t['activity.route.track'], { n: calls }) }
|
|
752
|
+
return { tone: 'busy', text: tFormat(t['activity.route.compare'], { n: calls }) }
|
|
753
|
+
}
|
|
754
|
+
if (active.stage === 'final') {
|
|
755
|
+
return { tone: 'busy', text: tFormat(t['activity.final.accepting'], { n: activityCount(active.expectedCalls, 1) }) }
|
|
756
|
+
}
|
|
757
|
+
// Process selection: the reply is buffered, so this is the only sign of life it has.
|
|
758
|
+
const candidates = activityCount(active.candidates, 2)
|
|
725
759
|
return active.phase === 'comparing'
|
|
726
760
|
? { tone: 'busy', text: tFormat(t['process.comparing'], { n: candidates }) }
|
|
727
761
|
: { tone: 'busy', text: tFormat(t['process.generating'], { n: candidates }) }
|
|
728
762
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
763
|
+
const settled = view?.settled
|
|
764
|
+
if (settled?.stage === 'final') {
|
|
765
|
+
if (settled.outcome === 'accepted') return { tone: 'ok', text: t['activity.final.accepted'] }
|
|
766
|
+
if (settled.outcome === 'rejected') return { tone: 'error', text: t['activity.final.rejected'] }
|
|
767
|
+
return null
|
|
768
|
+
}
|
|
769
|
+
switch (settled?.outcome) {
|
|
731
770
|
case 'replaced': return { tone: 'ok', text: t['process.replaced'] }
|
|
732
771
|
case 'kept': return { tone: 'ok', text: t['process.kept'] }
|
|
733
772
|
case 'same': return { tone: 'ok', text: t['process.same'] }
|