@qwen-code/arena-agent 0.0.8 → 0.0.9-dev.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/.env.example +42 -11
- package/README.md +119 -34
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +57 -36
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +68 -7
- package/dist/anthropic.js.map +1 -1
- package/dist/cli.js +10 -4
- package/dist/cli.js.map +1 -1
- package/dist/compaction.js +2 -1
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +73 -9
- package/dist/config.js.map +1 -1
- package/dist/conversation-metrics.d.ts +61 -0
- package/dist/conversation-metrics.js +433 -0
- package/dist/conversation-metrics.js.map +1 -0
- package/dist/history.d.ts +31 -2
- package/dist/history.js +53 -3
- package/dist/history.js.map +1 -1
- package/dist/metrics.d.ts +61 -0
- package/dist/metrics.js +578 -0
- package/dist/metrics.js.map +1 -0
- package/dist/mock.js +153 -0
- package/dist/mock.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.js +9 -2
- package/dist/protocol.js.map +1 -1
- package/dist/provider.js +22 -8
- package/dist/provider.js.map +1 -1
- package/dist/runtime-assets.d.ts +3 -0
- package/dist/runtime-assets.js +16 -0
- package/dist/runtime-assets.js.map +1 -0
- package/dist/sdk.d.ts +28 -1
- package/dist/sdk.js +38 -0
- package/dist/sdk.js.map +1 -1
- package/dist/server.js +486 -36
- package/dist/server.js.map +1 -1
- package/dist/side-query.d.ts +31 -0
- package/dist/side-query.js +138 -0
- package/dist/side-query.js.map +1 -0
- package/dist/tools.js +122 -45
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +4 -3
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +155 -0
- package/dist/web/app.js +333 -10
- package/dist/web/app.js.map +1 -1
- package/docs/SDK.md +563 -0
- package/package.json +2 -1
- package/web/index.html +25 -0
- package/web/styles.css +109 -3
package/dist/types.d.ts
CHANGED
|
@@ -67,6 +67,158 @@ export interface WorkspaceContext {
|
|
|
67
67
|
}
|
|
68
68
|
export type Message = SystemMessage | UserTurnMessage | AssistantMessage | ToolMessage;
|
|
69
69
|
export type ModelMessage = Message;
|
|
70
|
+
export type BashFailureOrigin = "model" | "environment" | "expected_nonzero" | "ambiguous";
|
|
71
|
+
export type BashClassificationSource = "side_query" | "unclassified";
|
|
72
|
+
export interface SideQueryAudit {
|
|
73
|
+
kind: string;
|
|
74
|
+
promptVersion: string;
|
|
75
|
+
model: string;
|
|
76
|
+
inputHash: string;
|
|
77
|
+
attempts: number;
|
|
78
|
+
confidence?: number;
|
|
79
|
+
attemptUsage?: ModelUsage[];
|
|
80
|
+
usage?: ModelUsage;
|
|
81
|
+
error?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface BashCallMetric {
|
|
84
|
+
observationId: string;
|
|
85
|
+
toolCallId: string;
|
|
86
|
+
sequence: number;
|
|
87
|
+
command: string;
|
|
88
|
+
status: string | null;
|
|
89
|
+
exitCode: number | null;
|
|
90
|
+
failed: boolean;
|
|
91
|
+
timedOut: boolean;
|
|
92
|
+
interrupted: boolean;
|
|
93
|
+
stdoutExcerpt: string;
|
|
94
|
+
stderrExcerpt: string;
|
|
95
|
+
origin?: BashFailureOrigin;
|
|
96
|
+
originReason?: string;
|
|
97
|
+
originSource?: BashClassificationSource;
|
|
98
|
+
originAudit?: SideQueryAudit;
|
|
99
|
+
}
|
|
100
|
+
export type BashRecoveryEpisodeState = "recovering" | "recovered" | "gave_up" | "resolved_by_alternative" | "censored";
|
|
101
|
+
export interface BashRecoveryEpisode {
|
|
102
|
+
id: string;
|
|
103
|
+
startedByObservationId: string;
|
|
104
|
+
startedByToolCallId: string;
|
|
105
|
+
state: BashRecoveryEpisodeState;
|
|
106
|
+
followupObservationIds: string[];
|
|
107
|
+
callsToRecover?: number;
|
|
108
|
+
recoveredByObservationId?: string;
|
|
109
|
+
terminalReason?: string;
|
|
110
|
+
terminalAudit?: SideQueryAudit;
|
|
111
|
+
}
|
|
112
|
+
export interface BashRecoverySummary {
|
|
113
|
+
modelErrors: number;
|
|
114
|
+
recovered: number;
|
|
115
|
+
gaveUp: number;
|
|
116
|
+
averageCallsToRecover: number | null;
|
|
117
|
+
}
|
|
118
|
+
export interface BashRecoveryMetric {
|
|
119
|
+
policyVersion: string;
|
|
120
|
+
calls: BashCallMetric[];
|
|
121
|
+
episodes: BashRecoveryEpisode[];
|
|
122
|
+
summary: BashRecoverySummary;
|
|
123
|
+
}
|
|
124
|
+
export interface ToolCallMetric {
|
|
125
|
+
observationId: string;
|
|
126
|
+
toolCallId: string;
|
|
127
|
+
sequence: number;
|
|
128
|
+
name: string;
|
|
129
|
+
offeredToolNames: string[];
|
|
130
|
+
catalogSource: "request_snapshot" | "current_fallback" | "unknown";
|
|
131
|
+
hallucinated: boolean;
|
|
132
|
+
}
|
|
133
|
+
export interface ToolHallucinationSummary {
|
|
134
|
+
total: number;
|
|
135
|
+
hallucinated: number;
|
|
136
|
+
}
|
|
137
|
+
export interface ToolHallucinationMetric {
|
|
138
|
+
policyVersion: string;
|
|
139
|
+
calls: ToolCallMetric[];
|
|
140
|
+
summary: ToolHallucinationSummary;
|
|
141
|
+
}
|
|
142
|
+
export type FeedbackClassificationSource = "deterministic" | "side_query" | "unclassified";
|
|
143
|
+
export type CorrectionUserVerdict = "accepted" | "extended" | "redirected" | "rejected" | "going_nowhere" | "gave_up" | "none" | "ambiguous";
|
|
144
|
+
export interface UserFeedbackObservation {
|
|
145
|
+
id: string;
|
|
146
|
+
turnId: string;
|
|
147
|
+
sequence: number;
|
|
148
|
+
userTextExcerpt: string;
|
|
149
|
+
previousAssistantExcerpt: string;
|
|
150
|
+
praise: boolean | null;
|
|
151
|
+
praiseReason: string;
|
|
152
|
+
praiseConfidence: number;
|
|
153
|
+
complaint: boolean | null;
|
|
154
|
+
complaintReason: string;
|
|
155
|
+
complaintConfidence: number;
|
|
156
|
+
correction: boolean | null;
|
|
157
|
+
correctionReason: string;
|
|
158
|
+
correctionConfidence: number;
|
|
159
|
+
priorCorrectionVerdict: CorrectionUserVerdict;
|
|
160
|
+
verdictReason: string;
|
|
161
|
+
verdictConfidence: number;
|
|
162
|
+
classificationSource: FeedbackClassificationSource;
|
|
163
|
+
audit?: SideQueryAudit;
|
|
164
|
+
}
|
|
165
|
+
export type PraiseComplaintOutcome = "success" | "failure" | "tie" | "no_signal";
|
|
166
|
+
export interface PraiseComplaintSummary {
|
|
167
|
+
praise: number;
|
|
168
|
+
complaint: number;
|
|
169
|
+
result: PraiseComplaintOutcome;
|
|
170
|
+
}
|
|
171
|
+
export interface PraiseComplaintMetric {
|
|
172
|
+
policyVersion: string;
|
|
173
|
+
observations: UserFeedbackObservation[];
|
|
174
|
+
summary: PraiseComplaintSummary;
|
|
175
|
+
}
|
|
176
|
+
export type CorrectionAttemptStatus = "attempted" | "not_attempted" | "ambiguous" | "censored";
|
|
177
|
+
export type SteerabilityCorrectionState = "awaiting_user" | "ambiguous" | "accepted" | "extended" | "redirected" | "rejected" | "going_nowhere" | "gave_up" | "not_attempted" | "censored";
|
|
178
|
+
export interface SteerabilityCorrection {
|
|
179
|
+
id: string;
|
|
180
|
+
sequence: number;
|
|
181
|
+
correctionTurnId: string;
|
|
182
|
+
correctionObservationId: string;
|
|
183
|
+
userCorrectionExcerpt: string;
|
|
184
|
+
correctionReason: string;
|
|
185
|
+
agentTraceExcerpt: string;
|
|
186
|
+
attemptStatus: CorrectionAttemptStatus;
|
|
187
|
+
attemptReason: string;
|
|
188
|
+
attemptConfidence: number;
|
|
189
|
+
state: SteerabilityCorrectionState;
|
|
190
|
+
verdictTurnId?: string;
|
|
191
|
+
verdictObservationId?: string;
|
|
192
|
+
verdictReason?: string;
|
|
193
|
+
verdictConfidence?: number;
|
|
194
|
+
verdictSource?: "user_message" | "ui_vote";
|
|
195
|
+
userTurnsToVerdict?: number;
|
|
196
|
+
}
|
|
197
|
+
export interface SteerabilitySummary {
|
|
198
|
+
successful: number;
|
|
199
|
+
unsuccessful: number;
|
|
200
|
+
pending: number;
|
|
201
|
+
excluded: number;
|
|
202
|
+
rate: number | null;
|
|
203
|
+
}
|
|
204
|
+
export interface SteerabilityMetric {
|
|
205
|
+
policyVersion: string;
|
|
206
|
+
corrections: SteerabilityCorrection[];
|
|
207
|
+
summary: SteerabilitySummary;
|
|
208
|
+
}
|
|
209
|
+
export interface SessionMetrics {
|
|
210
|
+
schemaVersion: 3;
|
|
211
|
+
analyzerVersion: string;
|
|
212
|
+
revision: number;
|
|
213
|
+
updatedAt: string;
|
|
214
|
+
throughTurnId: string;
|
|
215
|
+
evaluationStatus: "ready" | "partial" | "error";
|
|
216
|
+
evaluationErrors: string[];
|
|
217
|
+
bashRecovery: BashRecoveryMetric;
|
|
218
|
+
toolHallucination: ToolHallucinationMetric;
|
|
219
|
+
praiseComplaint: PraiseComplaintMetric;
|
|
220
|
+
steerability: SteerabilityMetric;
|
|
221
|
+
}
|
|
70
222
|
export interface ToolDefinition {
|
|
71
223
|
type: "function";
|
|
72
224
|
function: {
|
|
@@ -110,6 +262,7 @@ export interface RepoContext {
|
|
|
110
262
|
/** 工作分支的基线 commit。 */
|
|
111
263
|
baseCommit: string;
|
|
112
264
|
}
|
|
265
|
+
export type ReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
113
266
|
export interface ModelRequest {
|
|
114
267
|
model: string;
|
|
115
268
|
messages: ModelMessage[];
|
|
@@ -117,6 +270,7 @@ export interface ModelRequest {
|
|
|
117
270
|
toolChoice: string;
|
|
118
271
|
temperature: number;
|
|
119
272
|
maxTokens: number;
|
|
273
|
+
reasoningEffort?: ReasoningEffort;
|
|
120
274
|
enableThinking: boolean;
|
|
121
275
|
thinkingBudget: number;
|
|
122
276
|
preserveThinking: boolean;
|
|
@@ -148,6 +302,7 @@ export interface AgentEvent {
|
|
|
148
302
|
error?: string;
|
|
149
303
|
attempt?: number;
|
|
150
304
|
usage?: ModelUsage;
|
|
305
|
+
offered_tool_names?: string[];
|
|
151
306
|
}
|
|
152
307
|
export interface QuestionOption {
|
|
153
308
|
id: string;
|
package/dist/web/app.js
CHANGED
|
@@ -14,6 +14,8 @@ const elements = {
|
|
|
14
14
|
artifactPreviewPath: document.querySelector("#artifact-preview-path"),
|
|
15
15
|
artifactTitle: document.querySelector("#artifact-title"),
|
|
16
16
|
artifactToggle: document.querySelector("#artifact-toggle"),
|
|
17
|
+
bashMetric: document.querySelector("#bash-metric"),
|
|
18
|
+
bashMetricValue: document.querySelector("#bash-metric-value"),
|
|
17
19
|
composer: document.querySelector("#composer"),
|
|
18
20
|
connection: document.querySelector("#connection"),
|
|
19
21
|
connectionLabel: document.querySelector("#connection-label"),
|
|
@@ -21,6 +23,8 @@ const elements = {
|
|
|
21
23
|
dismissBanner: document.querySelector("#dismiss-banner"),
|
|
22
24
|
emptyState: document.querySelector("#empty-state"),
|
|
23
25
|
fileInput: document.querySelector("#file-input"),
|
|
26
|
+
feedbackMetric: document.querySelector("#feedback-metric"),
|
|
27
|
+
feedbackMetricValue: document.querySelector("#feedback-metric-value"),
|
|
24
28
|
hidePromo: document.querySelector(".hide-promo"),
|
|
25
29
|
messages: document.querySelector("#messages"),
|
|
26
30
|
mobileSidebarToggle: document.querySelector("#mobile-sidebar-toggle"),
|
|
@@ -30,12 +34,18 @@ const elements = {
|
|
|
30
34
|
modelName: document.querySelector("#model-name"),
|
|
31
35
|
newChat: document.querySelector("#new-chat"),
|
|
32
36
|
prompt: document.querySelector("#prompt"),
|
|
37
|
+
retry: document.querySelector("#retry"),
|
|
33
38
|
send: document.querySelector("#send"),
|
|
34
39
|
sidebar: document.querySelector("#sidebar"),
|
|
35
40
|
sidebarToggle: document.querySelector("#sidebar-toggle"),
|
|
36
41
|
stop: document.querySelector("#stop"),
|
|
42
|
+
steerMetric: document.querySelector("#steer-metric"),
|
|
43
|
+
steerMetricValue: document.querySelector("#steer-metric-value"),
|
|
37
44
|
taskId: document.querySelector("#task-id"),
|
|
38
45
|
template: document.querySelector("#message-template"),
|
|
46
|
+
undo: document.querySelector("#undo"),
|
|
47
|
+
toolMetric: document.querySelector("#tool-metric"),
|
|
48
|
+
toolMetricValue: document.querySelector("#tool-metric-value"),
|
|
39
49
|
};
|
|
40
50
|
const state = {
|
|
41
51
|
session: null,
|
|
@@ -53,6 +63,11 @@ const state = {
|
|
|
53
63
|
artifacts: new Map(),
|
|
54
64
|
activeArtifact: "",
|
|
55
65
|
pendingFiles: [],
|
|
66
|
+
pendingAttachments: [],
|
|
67
|
+
canUndo: false,
|
|
68
|
+
undoing: false,
|
|
69
|
+
retryMessageId: null,
|
|
70
|
+
metrics: null,
|
|
56
71
|
visitId: localStorage.getItem("arena-visit-id") || uuidv7(),
|
|
57
72
|
generation: 0,
|
|
58
73
|
};
|
|
@@ -126,8 +141,17 @@ function enterDraft() {
|
|
|
126
141
|
state.sessionId = "";
|
|
127
142
|
localStorage.removeItem("arena-session-id");
|
|
128
143
|
elements.taskId.textContent = "New chat";
|
|
144
|
+
setRetryable(null);
|
|
129
145
|
setConnection("Ready", "ready");
|
|
130
146
|
}
|
|
147
|
+
function setRetryable(messageId) {
|
|
148
|
+
state.retryMessageId = messageId || null;
|
|
149
|
+
if (!elements.retry)
|
|
150
|
+
return;
|
|
151
|
+
elements.retry.hidden = !state.retryMessageId;
|
|
152
|
+
elements.retry.disabled =
|
|
153
|
+
state.busy || state.undoing || !state.retryMessageId;
|
|
154
|
+
}
|
|
131
155
|
async function createSession(generation) {
|
|
132
156
|
markStep("session", "active");
|
|
133
157
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -162,6 +186,8 @@ function clearConversation() {
|
|
|
162
186
|
state.tools.clear();
|
|
163
187
|
state.inputRequests.clear();
|
|
164
188
|
state.artifacts.clear();
|
|
189
|
+
renderSessionMetrics(null);
|
|
190
|
+
setRetryable(null);
|
|
165
191
|
clearPendingFiles();
|
|
166
192
|
resetArtifactPreview();
|
|
167
193
|
renderArtifacts();
|
|
@@ -181,7 +207,7 @@ function renderPendingFiles(error = "") {
|
|
|
181
207
|
remove.type = "button";
|
|
182
208
|
remove.setAttribute("aria-label", `Remove ${file.name}`);
|
|
183
209
|
remove.textContent = "×";
|
|
184
|
-
remove.disabled = state.busy;
|
|
210
|
+
remove.disabled = state.busy || state.undoing;
|
|
185
211
|
remove.addEventListener("click", () => {
|
|
186
212
|
const identity = fileIdentity(file);
|
|
187
213
|
state.pendingFiles = state.pendingFiles.filter((candidate) => fileIdentity(candidate) !== identity);
|
|
@@ -191,6 +217,25 @@ function renderPendingFiles(error = "") {
|
|
|
191
217
|
chip.append(name, remove);
|
|
192
218
|
elements.attachmentList.append(chip);
|
|
193
219
|
}
|
|
220
|
+
for (const attachment of state.pendingAttachments) {
|
|
221
|
+
const chip = document.createElement("div");
|
|
222
|
+
chip.className = "attachment-chip";
|
|
223
|
+
const name = document.createElement("span");
|
|
224
|
+
name.textContent = attachment.name;
|
|
225
|
+
name.title = `${attachment.name} · ${Math.max(1, Math.ceil(attachment.size / 1024))} KB`;
|
|
226
|
+
const remove = document.createElement("button");
|
|
227
|
+
remove.type = "button";
|
|
228
|
+
remove.setAttribute("aria-label", `Remove ${attachment.name}`);
|
|
229
|
+
remove.textContent = "×";
|
|
230
|
+
remove.disabled = state.busy || state.undoing;
|
|
231
|
+
remove.addEventListener("click", () => {
|
|
232
|
+
state.pendingAttachments = state.pendingAttachments.filter((candidate) => candidate.path !== attachment.path);
|
|
233
|
+
renderPendingFiles();
|
|
234
|
+
syncSendState();
|
|
235
|
+
});
|
|
236
|
+
chip.append(name, remove);
|
|
237
|
+
elements.attachmentList.append(chip);
|
|
238
|
+
}
|
|
194
239
|
if (error) {
|
|
195
240
|
const message = document.createElement("span");
|
|
196
241
|
message.className = "attachment-error";
|
|
@@ -198,13 +243,16 @@ function renderPendingFiles(error = "") {
|
|
|
198
243
|
elements.attachmentList.append(message);
|
|
199
244
|
}
|
|
200
245
|
elements.attachmentList.hidden =
|
|
201
|
-
state.pendingFiles.length === 0 &&
|
|
246
|
+
state.pendingFiles.length === 0 &&
|
|
247
|
+
state.pendingAttachments.length === 0 &&
|
|
248
|
+
!error;
|
|
202
249
|
}
|
|
203
250
|
function addPendingFiles(files) {
|
|
204
251
|
const existing = new Set(state.pendingFiles.map(fileIdentity));
|
|
205
252
|
let error = "";
|
|
206
253
|
for (const file of files) {
|
|
207
|
-
if (state.pendingFiles.length
|
|
254
|
+
if (state.pendingFiles.length + state.pendingAttachments.length >=
|
|
255
|
+
maxAttachmentCount) {
|
|
208
256
|
error = `Up to ${maxAttachmentCount} files per message`;
|
|
209
257
|
break;
|
|
210
258
|
}
|
|
@@ -223,6 +271,7 @@ function addPendingFiles(files) {
|
|
|
223
271
|
}
|
|
224
272
|
function clearPendingFiles() {
|
|
225
273
|
state.pendingFiles = [];
|
|
274
|
+
state.pendingAttachments = [];
|
|
226
275
|
if (elements.fileInput)
|
|
227
276
|
elements.fileInput.value = "";
|
|
228
277
|
if (elements.attachmentList)
|
|
@@ -638,6 +687,32 @@ function renderHistory(messages) {
|
|
|
638
687
|
state.currentReasoning = null;
|
|
639
688
|
elements.conversation.scrollTop = elements.conversation.scrollHeight;
|
|
640
689
|
}
|
|
690
|
+
function applyUndo(result) {
|
|
691
|
+
if (!result?.undone && !Array.isArray(result?.messages))
|
|
692
|
+
return;
|
|
693
|
+
clearConversation();
|
|
694
|
+
renderHistory(result.messages || []);
|
|
695
|
+
renderSessionMetrics(result.metrics || null);
|
|
696
|
+
state.pendingAttachments = [...(result.attachments || [])];
|
|
697
|
+
state.canUndo = result.canUndo === true;
|
|
698
|
+
elements.prompt.value = result.input || "";
|
|
699
|
+
resizePrompt();
|
|
700
|
+
renderPendingFiles();
|
|
701
|
+
syncSendState();
|
|
702
|
+
syncUndoState();
|
|
703
|
+
setConnection("Ready", "ready");
|
|
704
|
+
elements.prompt.focus();
|
|
705
|
+
}
|
|
706
|
+
function replaceRenderedHistory(messages) {
|
|
707
|
+
elements.messages.replaceChildren();
|
|
708
|
+
elements.emptyState.hidden = false;
|
|
709
|
+
document.body.classList.remove("has-messages");
|
|
710
|
+
state.currentAssistant = null;
|
|
711
|
+
state.currentReasoning = null;
|
|
712
|
+
state.tools.clear();
|
|
713
|
+
state.inputRequests.clear();
|
|
714
|
+
renderHistory(messages);
|
|
715
|
+
}
|
|
641
716
|
function parseJson(value) {
|
|
642
717
|
if (typeof value !== "string")
|
|
643
718
|
return value;
|
|
@@ -653,23 +728,195 @@ async function loadHistory() {
|
|
|
653
728
|
return;
|
|
654
729
|
const value = await state.session.getHistory();
|
|
655
730
|
renderHistory(value.messages || []);
|
|
731
|
+
state.canUndo = value.canUndo === true;
|
|
732
|
+
syncUndoState();
|
|
733
|
+
renderSessionMetrics(value.metrics || null);
|
|
734
|
+
setRetryable(value.retryable ? value.retryMessageId : null);
|
|
656
735
|
if (value.activePrompt)
|
|
657
736
|
addMessage("user", value.activePrompt);
|
|
658
737
|
}
|
|
738
|
+
function metricNumber(value) {
|
|
739
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
740
|
+
}
|
|
741
|
+
function renderSessionMetrics(metrics) {
|
|
742
|
+
if (!metrics) {
|
|
743
|
+
state.metrics = null;
|
|
744
|
+
elements.bashMetric.dataset.state = "neutral";
|
|
745
|
+
elements.toolMetric.dataset.state = "neutral";
|
|
746
|
+
elements.feedbackMetric.dataset.state = "neutral";
|
|
747
|
+
elements.steerMetric.dataset.state = "neutral";
|
|
748
|
+
elements.bashMetricValue.textContent = "—";
|
|
749
|
+
elements.toolMetricValue.textContent = "—";
|
|
750
|
+
elements.feedbackMetricValue.textContent = "—";
|
|
751
|
+
elements.steerMetricValue.textContent = "—";
|
|
752
|
+
elements.bashMetricValue.dataset.mobileValue = "B—";
|
|
753
|
+
elements.toolMetricValue.dataset.mobileValue = "T—";
|
|
754
|
+
elements.feedbackMetricValue.dataset.mobileValue = "P—";
|
|
755
|
+
elements.steerMetricValue.dataset.mobileValue = "S—";
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (state.metrics &&
|
|
759
|
+
metricNumber(metrics.revision) < metricNumber(state.metrics.revision)) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
state.metrics = metrics;
|
|
763
|
+
const bash = metrics.bashRecovery?.summary || {};
|
|
764
|
+
const tools = metrics.toolHallucination?.summary || {};
|
|
765
|
+
const feedback = metrics.praiseComplaint?.summary || {};
|
|
766
|
+
const steer = metrics.steerability?.summary || {};
|
|
767
|
+
const feedbackObservations = metrics.praiseComplaint?.observations || [];
|
|
768
|
+
const corrections = metrics.steerability?.corrections || [];
|
|
769
|
+
const bashCalls = metrics.bashRecovery?.calls || [];
|
|
770
|
+
const bashEpisodes = metrics.bashRecovery?.episodes || [];
|
|
771
|
+
const praise = metricNumber(feedback.praise);
|
|
772
|
+
const complaints = metricNumber(feedback.complaint);
|
|
773
|
+
const feedbackAmbiguous = feedbackObservations.filter((item) => item.praise === null || item.complaint === null).length;
|
|
774
|
+
const feedbackOutcome = feedback.result || "no_signal";
|
|
775
|
+
elements.feedbackMetric.dataset.state =
|
|
776
|
+
feedbackOutcome === "success"
|
|
777
|
+
? "good"
|
|
778
|
+
: feedbackOutcome === "failure"
|
|
779
|
+
? "bad"
|
|
780
|
+
: feedbackOutcome === "tie" || feedbackAmbiguous
|
|
781
|
+
? "warning"
|
|
782
|
+
: "neutral";
|
|
783
|
+
elements.feedbackMetricValue.textContent =
|
|
784
|
+
feedbackOutcome === "no_signal"
|
|
785
|
+
? "No feedback"
|
|
786
|
+
: `${praise} praise · ${complaints} complaint`;
|
|
787
|
+
elements.feedbackMetricValue.dataset.mobileValue =
|
|
788
|
+
feedbackOutcome === "no_signal" ? "P—" : `P${praise}:${complaints}`;
|
|
789
|
+
elements.feedbackMetric.title = [
|
|
790
|
+
`${praise} praise messages`,
|
|
791
|
+
`${complaints} complaint messages`,
|
|
792
|
+
`${feedbackAmbiguous} unclassified`,
|
|
793
|
+
`outcome ${feedbackOutcome}`,
|
|
794
|
+
].join(" · ");
|
|
795
|
+
const steerSuccess = metricNumber(steer.successful);
|
|
796
|
+
const steerFailure = metricNumber(steer.unsuccessful);
|
|
797
|
+
const steerAwaiting = metricNumber(steer.pending);
|
|
798
|
+
const steerExcluded = metricNumber(steer.excluded);
|
|
799
|
+
const steerAmbiguous = corrections.filter((item) => item.state === "ambiguous").length;
|
|
800
|
+
const steerResolved = steerSuccess + steerFailure;
|
|
801
|
+
elements.steerMetric.dataset.state = steerFailure
|
|
802
|
+
? "bad"
|
|
803
|
+
: steerAwaiting || steerAmbiguous
|
|
804
|
+
? "warning"
|
|
805
|
+
: steerSuccess
|
|
806
|
+
? "good"
|
|
807
|
+
: "neutral";
|
|
808
|
+
elements.steerMetricValue.textContent = steerFailure
|
|
809
|
+
? `${steerSuccess}/${steerResolved} success`
|
|
810
|
+
: steerAwaiting
|
|
811
|
+
? `${steerAwaiting} awaiting user`
|
|
812
|
+
: steerSuccess
|
|
813
|
+
? `${steerSuccess}/${steerResolved} success`
|
|
814
|
+
: "No corrections";
|
|
815
|
+
elements.steerMetricValue.dataset.mobileValue = steerResolved
|
|
816
|
+
? `S${steerSuccess}:${steerFailure}`
|
|
817
|
+
: steerAwaiting
|
|
818
|
+
? `S…${steerAwaiting}`
|
|
819
|
+
: "S—";
|
|
820
|
+
elements.steerMetric.title = [
|
|
821
|
+
`${steerSuccess + steerFailure + steerAwaiting + steerExcluded} corrections`,
|
|
822
|
+
`${steerSuccess} successful`,
|
|
823
|
+
`${steerFailure} unsuccessful`,
|
|
824
|
+
`${steerAwaiting} awaiting explicit user verdict`,
|
|
825
|
+
`${steerExcluded} excluded`,
|
|
826
|
+
].join(" · ");
|
|
827
|
+
const modelErrors = metricNumber(bash.modelErrors);
|
|
828
|
+
const recovering = bashEpisodes.filter((episode) => episode.state === "recovering").length;
|
|
829
|
+
const gaveUp = metricNumber(bash.gaveUp);
|
|
830
|
+
const recovered = metricNumber(bash.recovered);
|
|
831
|
+
const ambiguous = bashCalls.filter((call) => call.origin === "ambiguous").length;
|
|
832
|
+
const average = bash.averageCallsToRecover;
|
|
833
|
+
if (gaveUp) {
|
|
834
|
+
elements.bashMetric.dataset.state = "bad";
|
|
835
|
+
elements.bashMetricValue.textContent = `${gaveUp} gave up`;
|
|
836
|
+
}
|
|
837
|
+
else if (recovering || ambiguous) {
|
|
838
|
+
elements.bashMetric.dataset.state = "warning";
|
|
839
|
+
elements.bashMetricValue.textContent = recovering
|
|
840
|
+
? `${recovering} recovering`
|
|
841
|
+
: `${ambiguous} unclassified`;
|
|
842
|
+
}
|
|
843
|
+
else if (recovered) {
|
|
844
|
+
elements.bashMetric.dataset.state = "good";
|
|
845
|
+
elements.bashMetricValue.textContent = `${Number(average).toFixed(1)} calls avg`;
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
elements.bashMetric.dataset.state = "good";
|
|
849
|
+
elements.bashMetricValue.textContent = modelErrors ? "Resolved" : "No model errors";
|
|
850
|
+
}
|
|
851
|
+
elements.bashMetricValue.dataset.mobileValue = gaveUp
|
|
852
|
+
? `B!${gaveUp}`
|
|
853
|
+
: recovering
|
|
854
|
+
? `B…${recovering}`
|
|
855
|
+
: ambiguous
|
|
856
|
+
? `B?${ambiguous}`
|
|
857
|
+
: recovered
|
|
858
|
+
? `B${Number(average).toFixed(1)}`
|
|
859
|
+
: "B✓";
|
|
860
|
+
elements.bashMetric.title = [
|
|
861
|
+
`${bashCalls.length} Bash calls`,
|
|
862
|
+
`${modelErrors} model errors`,
|
|
863
|
+
`${recovered} recovered`,
|
|
864
|
+
`${ambiguous} ambiguous`,
|
|
865
|
+
].join(" · ");
|
|
866
|
+
const hallucinated = metricNumber(tools.hallucinated);
|
|
867
|
+
const totalTools = metricNumber(tools.total);
|
|
868
|
+
elements.toolMetric.dataset.state = hallucinated ? "bad" : "good";
|
|
869
|
+
elements.toolMetricValue.textContent = hallucinated
|
|
870
|
+
? `${hallucinated} invalid`
|
|
871
|
+
: "Clean";
|
|
872
|
+
elements.toolMetricValue.dataset.mobileValue = hallucinated
|
|
873
|
+
? `T!${hallucinated}`
|
|
874
|
+
: "T✓";
|
|
875
|
+
elements.toolMetric.title = `${hallucinated} hallucinated of ${totalTools} structured tool calls`;
|
|
876
|
+
}
|
|
659
877
|
function setBusy(busy) {
|
|
660
878
|
state.busy = busy;
|
|
661
879
|
elements.send.disabled =
|
|
662
|
-
busy ||
|
|
880
|
+
busy ||
|
|
881
|
+
state.undoing ||
|
|
882
|
+
(!elements.prompt.value.trim() &&
|
|
883
|
+
!state.pendingFiles.length &&
|
|
884
|
+
!state.pendingAttachments.length);
|
|
663
885
|
elements.stop.hidden = !busy;
|
|
664
|
-
elements.prompt.disabled = busy;
|
|
665
|
-
elements.addFiles.disabled = busy;
|
|
666
|
-
elements.fileInput.disabled = busy;
|
|
886
|
+
elements.prompt.disabled = busy || state.undoing;
|
|
887
|
+
elements.addFiles.disabled = busy || state.undoing;
|
|
888
|
+
elements.fileInput.disabled = busy || state.undoing;
|
|
889
|
+
if (elements.retry) {
|
|
890
|
+
elements.retry.disabled =
|
|
891
|
+
busy || state.undoing || !state.retryMessageId;
|
|
892
|
+
}
|
|
667
893
|
renderPendingFiles();
|
|
894
|
+
syncUndoState();
|
|
668
895
|
}
|
|
669
896
|
function syncSendState() {
|
|
670
897
|
elements.send.disabled =
|
|
671
898
|
state.busy ||
|
|
672
|
-
|
|
899
|
+
state.undoing ||
|
|
900
|
+
(!elements.prompt.value.trim() &&
|
|
901
|
+
!state.pendingFiles.length &&
|
|
902
|
+
!state.pendingAttachments.length);
|
|
903
|
+
}
|
|
904
|
+
function syncUndoState() {
|
|
905
|
+
if (!elements.undo)
|
|
906
|
+
return;
|
|
907
|
+
elements.undo.hidden =
|
|
908
|
+
state.busy || !state.session || !state.canUndo;
|
|
909
|
+
elements.undo.disabled = state.undoing;
|
|
910
|
+
}
|
|
911
|
+
function setUndoing(undoing) {
|
|
912
|
+
state.undoing = undoing;
|
|
913
|
+
elements.prompt.disabled = undoing || state.busy;
|
|
914
|
+
elements.addFiles.disabled = undoing || state.busy;
|
|
915
|
+
elements.fileInput.disabled = undoing || state.busy;
|
|
916
|
+
renderPendingFiles();
|
|
917
|
+
syncSendState();
|
|
918
|
+
syncUndoState();
|
|
919
|
+
setRetryable(state.retryMessageId);
|
|
673
920
|
}
|
|
674
921
|
function setSidebarExpanded(expanded) {
|
|
675
922
|
if (!elements.sidebar)
|
|
@@ -701,6 +948,7 @@ function setArtifactPanelOpen(open) {
|
|
|
701
948
|
function handleStreamEvent(event) {
|
|
702
949
|
switch (event.type) {
|
|
703
950
|
case "start":
|
|
951
|
+
setRetryable(null);
|
|
704
952
|
markStep("model", "active");
|
|
705
953
|
setBusy(true);
|
|
706
954
|
startActivity("Starting…");
|
|
@@ -795,6 +1043,15 @@ function handleStreamEvent(event) {
|
|
|
795
1043
|
case "data-present-file":
|
|
796
1044
|
presentArtifact(event.data);
|
|
797
1045
|
break;
|
|
1046
|
+
case "data-turn-undone":
|
|
1047
|
+
applyUndo({ undone: true, canUndo: false, ...(event.data || {}) });
|
|
1048
|
+
break;
|
|
1049
|
+
case "data-messages":
|
|
1050
|
+
replaceRenderedHistory(event.data?.messages || []);
|
|
1051
|
+
break;
|
|
1052
|
+
case "data-session-metrics":
|
|
1053
|
+
renderSessionMetrics(event.data?.metrics || null);
|
|
1054
|
+
break;
|
|
798
1055
|
case "error":
|
|
799
1056
|
stopActivity();
|
|
800
1057
|
addMessage("assistant", event.errorText || "The agent stopped unexpectedly.", "error");
|
|
@@ -807,6 +1064,9 @@ function handleStreamEvent(event) {
|
|
|
807
1064
|
state.currentReasoning = null;
|
|
808
1065
|
markStep("model", "complete");
|
|
809
1066
|
setConnection(event.status === "success" ? "Ready" : "Agent error", event.status === "success" ? "ready" : "error");
|
|
1067
|
+
state.canUndo = true;
|
|
1068
|
+
setRetryable(event.status === "error" && event.retryable ? event.messageId : null);
|
|
1069
|
+
syncUndoState();
|
|
810
1070
|
elements.prompt.disabled = false;
|
|
811
1071
|
elements.prompt.focus();
|
|
812
1072
|
break;
|
|
@@ -873,12 +1133,19 @@ async function connectStream() {
|
|
|
873
1133
|
async function submit(prompt) {
|
|
874
1134
|
const value = prompt.trim();
|
|
875
1135
|
const files = [...state.pendingFiles];
|
|
876
|
-
|
|
1136
|
+
const attachments = [...state.pendingAttachments];
|
|
1137
|
+
if ((!value && !files.length && !attachments.length) ||
|
|
1138
|
+
state.busy ||
|
|
1139
|
+
state.undoing)
|
|
877
1140
|
return;
|
|
878
1141
|
const generation = state.generation;
|
|
879
1142
|
addMessage("user", [
|
|
880
1143
|
value,
|
|
881
|
-
files.length
|
|
1144
|
+
files.length || attachments.length
|
|
1145
|
+
? `Attached: ${[...files, ...attachments]
|
|
1146
|
+
.map((file) => file.name)
|
|
1147
|
+
.join(", ")}`
|
|
1148
|
+
: "",
|
|
882
1149
|
]
|
|
883
1150
|
.filter(Boolean)
|
|
884
1151
|
.join("\n"));
|
|
@@ -899,6 +1166,7 @@ async function submit(prompt) {
|
|
|
899
1166
|
const sent = await state.session.send({
|
|
900
1167
|
input: value,
|
|
901
1168
|
files,
|
|
1169
|
+
attachments,
|
|
902
1170
|
messageId: uuidv7(),
|
|
903
1171
|
metadata: {
|
|
904
1172
|
arenaVisitId: state.visitId,
|
|
@@ -920,6 +1188,35 @@ async function submit(prompt) {
|
|
|
920
1188
|
setConnection("Request failed", "error");
|
|
921
1189
|
}
|
|
922
1190
|
}
|
|
1191
|
+
async function retryFailedTurn() {
|
|
1192
|
+
if (!state.session ||
|
|
1193
|
+
!state.retryMessageId ||
|
|
1194
|
+
state.busy ||
|
|
1195
|
+
state.undoing)
|
|
1196
|
+
return;
|
|
1197
|
+
const generation = state.generation;
|
|
1198
|
+
const failedMessageId = state.retryMessageId;
|
|
1199
|
+
setBusy(true);
|
|
1200
|
+
startActivity("Retrying…");
|
|
1201
|
+
try {
|
|
1202
|
+
await connectStream();
|
|
1203
|
+
if (generation !== state.generation)
|
|
1204
|
+
return;
|
|
1205
|
+
await state.session.retry({ messageId: failedMessageId });
|
|
1206
|
+
if (generation !== state.generation)
|
|
1207
|
+
return;
|
|
1208
|
+
setRetryable(null);
|
|
1209
|
+
}
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
if (generation !== state.generation)
|
|
1212
|
+
return;
|
|
1213
|
+
stopActivity();
|
|
1214
|
+
addMessage("assistant", error.message, "error");
|
|
1215
|
+
setBusy(false);
|
|
1216
|
+
setRetryable(failedMessageId);
|
|
1217
|
+
setConnection("Retry failed", "error");
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
923
1220
|
async function cancelTurn() {
|
|
924
1221
|
if (!state.session) {
|
|
925
1222
|
stopActivity();
|
|
@@ -936,6 +1233,28 @@ async function cancelTurn() {
|
|
|
936
1233
|
setConnection("Ready", "ready");
|
|
937
1234
|
}
|
|
938
1235
|
}
|
|
1236
|
+
async function undoTurn() {
|
|
1237
|
+
if (!state.session || state.busy || state.undoing || !state.canUndo)
|
|
1238
|
+
return;
|
|
1239
|
+
setUndoing(true);
|
|
1240
|
+
setConnection("Undoing…", "connecting");
|
|
1241
|
+
try {
|
|
1242
|
+
const result = await state.session.undo();
|
|
1243
|
+
if (result.undone)
|
|
1244
|
+
applyUndo(result);
|
|
1245
|
+
else {
|
|
1246
|
+
state.canUndo = false;
|
|
1247
|
+
syncUndoState();
|
|
1248
|
+
setConnection("Nothing to undo", "ready");
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
setConnection(error.message || "Undo failed", "error");
|
|
1253
|
+
}
|
|
1254
|
+
finally {
|
|
1255
|
+
setUndoing(false);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
939
1258
|
function resizePrompt() {
|
|
940
1259
|
elements.prompt.style.height = "auto";
|
|
941
1260
|
elements.prompt.style.height = `${Math.min(elements.prompt.scrollHeight, 180)}px`;
|
|
@@ -948,6 +1267,8 @@ function newChat() {
|
|
|
948
1267
|
state.connecting = false;
|
|
949
1268
|
clearPendingFiles();
|
|
950
1269
|
clearConversation();
|
|
1270
|
+
state.canUndo = false;
|
|
1271
|
+
state.undoing = false;
|
|
951
1272
|
setBusy(false);
|
|
952
1273
|
enterDraft();
|
|
953
1274
|
if (location.pathname !== "/") {
|
|
@@ -1010,6 +1331,8 @@ elements.prompt.addEventListener("keydown", (event) => {
|
|
|
1010
1331
|
}
|
|
1011
1332
|
});
|
|
1012
1333
|
elements.stop.addEventListener("click", cancelTurn);
|
|
1334
|
+
elements.undo?.addEventListener("click", undoTurn);
|
|
1335
|
+
elements.retry?.addEventListener("click", retryFailedTurn);
|
|
1013
1336
|
elements.newChat.addEventListener("click", newChat);
|
|
1014
1337
|
elements.sidebarToggle?.addEventListener("click", () => {
|
|
1015
1338
|
setSidebarExpanded(!elements.sidebar?.classList.contains("is-expanded"));
|