@evandu/super-pi 0.1.99 → 0.1.100
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.
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"src/client/types.ts": "6487df00c164370550b5977f9b23b17e22aff4bfb59dfe1e52200042340e3769",
|
|
25
25
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
26
26
|
},
|
|
27
|
-
"recordedAt": "2026-10-21T05:09
|
|
27
|
+
"recordedAt": "2026-10-21T05:29:09.656Z"
|
|
28
28
|
}
|
package/package.json
CHANGED
package/scripts/diagnose.mjs
CHANGED
|
@@ -121,6 +121,44 @@ export async function diagnose({ repositoryRoot, turns = 2, onProgress = () => {
|
|
|
121
121
|
result.catalogSource = catalog.catalog?.source ?? catalog.source ?? "unknown";
|
|
122
122
|
result.models = (catalog.catalog?.models ?? catalog.models ?? []).length;
|
|
123
123
|
|
|
124
|
+
// Span 1b: THE PICKER AFTER A WORKSPACE DEFAULT IS SET.
|
|
125
|
+
//
|
|
126
|
+
// THIS IS THE PATH THE FIRST MEASUREMENT MISSED, and it is the one most people are actually on.
|
|
127
|
+
// `createConversation` copies the workspace's default model onto the new conversation, and it
|
|
128
|
+
// VALIDATES that default against the catalog first - so creating a conversation with a default
|
|
129
|
+
// model READS THE CATALOG, and a picker opened afterwards asks for the LEVELS of that model, which
|
|
130
|
+
// is a different catalog key. A measurement that never sets a default model never sees any of it.
|
|
131
|
+
//
|
|
132
|
+
// The report says which path it measured, because a number for a path nobody is on is worse than
|
|
133
|
+
// no number at all.
|
|
134
|
+
const defaultModel = (catalog.catalog?.models ?? catalog.models ?? []).find((m) => m.modelId)
|
|
135
|
+
?? (catalog.catalog?.defaultModel ?? catalog.defaultModel);
|
|
136
|
+
if (defaultModel) {
|
|
137
|
+
onProgress("setting a workspace default model, then creating a conversation...");
|
|
138
|
+
send({
|
|
139
|
+
type: "workspace.settings",
|
|
140
|
+
workspaceId: "workspace_default",
|
|
141
|
+
model: { provider: defaultModel.provider, modelId: defaultModel.modelId },
|
|
142
|
+
thinkingLevel: null,
|
|
143
|
+
});
|
|
144
|
+
await sleep(400);
|
|
145
|
+
const withDefaultStart = Date.now();
|
|
146
|
+
send({ type: "conversation.create", workspaceId: "workspace_default" });
|
|
147
|
+
const withDefault = await waitFor((e) => e.type === "state"
|
|
148
|
+
&& e.state.conversations.length >= 2, "a conversation with the workspace default");
|
|
149
|
+
result.conversationWithDefaultMs = Date.now() - withDefaultStart;
|
|
150
|
+
const withDefaultConversation = events.filter((e) => e.type === "state").at(-1).state.conversations[0];
|
|
151
|
+
// And the picker for THAT conversation, which is the one a person actually opens.
|
|
152
|
+
const defaultPickerStart = Date.now();
|
|
153
|
+
send({ type: "catalog.fetch", conversationId: withDefaultConversation.id });
|
|
154
|
+
// The conversation id is a SIBLING of the catalog, not inside it - `broadcastCatalog` sends
|
|
155
|
+
// `{ type, catalog, conversationId }` - and reading it from the wrong place times out on a
|
|
156
|
+
// catalog that has already arrived.
|
|
157
|
+
await waitFor((e) => e.type === "model.catalog" && e.conversationId === withDefaultConversation.id,
|
|
158
|
+
"the catalog for the conversation with a default model");
|
|
159
|
+
result.pickerWithDefaultMs = Date.now() - defaultPickerStart;
|
|
160
|
+
}
|
|
161
|
+
|
|
124
162
|
// Spans 2 and 3, per turn.
|
|
125
163
|
result.turns = [];
|
|
126
164
|
for (let turn = 1; turn <= turns; turn += 1) {
|
|
@@ -153,6 +191,65 @@ export async function diagnose({ repositoryRoot, turns = 2, onProgress = () => {
|
|
|
153
191
|
reused: (added.match(/phase=session\.reused/g) ?? []).length,
|
|
154
192
|
});
|
|
155
193
|
}
|
|
194
|
+
// Span 4: THE INTERRUPT.
|
|
195
|
+
//
|
|
196
|
+
// The second thing people report is "打断并回答 → 开始回答还要好十秒", and it is a DIFFERENT
|
|
197
|
+
// chain from sending a message: it waits for the running turn to unwind, waits for its slot, and
|
|
198
|
+
// only then asks the model. Those first two waits are Super Pi's, and they are the ones that can
|
|
199
|
+
// be slow; the model's share is the same as any other turn.
|
|
200
|
+
//
|
|
201
|
+
// So it is measured as its own span, with a tool running so there is something to interrupt.
|
|
202
|
+
onProgress("measuring the interrupt...");
|
|
203
|
+
const interruptMark = events.length;
|
|
204
|
+
const interruptLogMark = log.length;
|
|
205
|
+
send({
|
|
206
|
+
type: "message.send",
|
|
207
|
+
conversationId: conversation.id,
|
|
208
|
+
content: '用 bash 运行:node -e "setTimeout(()=>{},20000)"。等它跑完再回复。',
|
|
209
|
+
});
|
|
210
|
+
let toolStarted = false;
|
|
211
|
+
const toolDeadline = Date.now() + 60000;
|
|
212
|
+
while (!toolStarted && Date.now() < toolDeadline) {
|
|
213
|
+
toolStarted = events.slice(interruptMark).some((e) => e.type === "message.tools");
|
|
214
|
+
if (!toolStarted) await sleep(30);
|
|
215
|
+
}
|
|
216
|
+
if (toolStarted) {
|
|
217
|
+
// Queue the message that the interrupt will answer.
|
|
218
|
+
send({ type: "message.send", conversationId: conversation.id, content: "被打断的那条:只回答「第二」两个字。" });
|
|
219
|
+
let queued = null;
|
|
220
|
+
const queueDeadline = Date.now() + 60000;
|
|
221
|
+
while (!queued && Date.now() < queueDeadline) {
|
|
222
|
+
queued = events.filter((e) => e.type === "state").at(-1)?.state?.conversations
|
|
223
|
+
?.find((c) => c.id === conversation.id)?.messages
|
|
224
|
+
?.find((m) => m.content?.includes("被打断的那条") && m.delivery === "waiting");
|
|
225
|
+
if (!queued) await sleep(30);
|
|
226
|
+
}
|
|
227
|
+
if (queued) {
|
|
228
|
+
await sleep(800);
|
|
229
|
+
const clicked = Date.now();
|
|
230
|
+
send({ type: "message.interrupt", conversationId: conversation.id, messageId: queued.id });
|
|
231
|
+
let interruptWaiting = 0;
|
|
232
|
+
try {
|
|
233
|
+
await waitFor((e, i) => i >= interruptMark && e.type === "state"
|
|
234
|
+
&& e.state.conversations.find((c) => c.id === conversation.id)?.status === "waiting"
|
|
235
|
+
&& Date.now() > clicked, "the interrupted turn to report waiting", 60000);
|
|
236
|
+
interruptWaiting = Date.now() - clicked;
|
|
237
|
+
await waitFor((e, i) => i >= interruptMark && e.type === "message.delta" && e.conversationId === conversation.id
|
|
238
|
+
&& Date.now() > clicked + 50, "the interrupted turn's first character", 180000);
|
|
239
|
+
} catch { /* reported as not measured below */ }
|
|
240
|
+
const interruptText = Date.now() - clicked;
|
|
241
|
+
const interruptLog = log.slice(interruptLogMark);
|
|
242
|
+
result.interrupt = {
|
|
243
|
+
toWaitingMs: interruptWaiting || undefined,
|
|
244
|
+
toTextMs: interruptText,
|
|
245
|
+
modelMs: interruptText - (interruptWaiting || 0),
|
|
246
|
+
spawned: (interruptLog.match(/phase=pi\.spawned/g) ?? []).length,
|
|
247
|
+
built: (interruptLog.match(/phase=session\.build\.begin/g) ?? []).length,
|
|
248
|
+
reused: (interruptLog.match(/phase=session\.reused/g) ?? []).length,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
156
253
|
socket.close();
|
|
157
254
|
} catch (error) {
|
|
158
255
|
result.error = error instanceof Error ? error.message : String(error);
|
|
@@ -185,10 +282,27 @@ export function renderReport(result) {
|
|
|
185
282
|
lines.push("");
|
|
186
283
|
lines.push(" 每一段耗时 耗时 归属");
|
|
187
284
|
lines.push(` 1. picker 有数据 ${ms(result.pickerMs)} Super Pi (catalog 读取)`);
|
|
285
|
+
if (typeof result.conversationWithDefaultMs === "number") {
|
|
286
|
+
lines.push(` 1b. 建会话(工作区有默认模型) ${ms(result.conversationWithDefaultMs)} Super Pi (建会话要校验默认模型,会再读一次 catalog)`);
|
|
287
|
+
lines.push(` 1c. 该会话的 picker ${ms(result.pickerWithDefaultMs)} Super Pi (带默认模型时读的是另一份 catalog)`);
|
|
288
|
+
} else {
|
|
289
|
+
lines.push(" 1b/1c. 未测:这次没有可用的模型来设工作区默认模型");
|
|
290
|
+
}
|
|
188
291
|
for (const turn of result.turns ?? []) {
|
|
189
292
|
lines.push(` 第${turn.turn}轮 发送 -> 等待中 ${ms(turn.toWaitingMs)} Super Pi (建会话 + 首个 prompt)`);
|
|
190
293
|
lines.push(` 第${turn.turn}轮 等待中 -> 出字 ${ms(turn.modelMs)} 模型 (不是 Super Pi)`);
|
|
191
294
|
}
|
|
295
|
+
if (result.interrupt) {
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push(" 打断(点「打断并回答」之后):");
|
|
298
|
+
lines.push(` 点击 -> 新一轮报等待中 ${ms(result.interrupt.toWaitingMs)} Super Pi (等旧轮退完 + 等槽位)`);
|
|
299
|
+
lines.push(` 点击 -> 出字 ${ms(result.interrupt.toTextMs)} 整条链`);
|
|
300
|
+
lines.push(` 其中模型 ${ms(result.interrupt.modelMs)} 模型 (不是 Super Pi)`);
|
|
301
|
+
lines.push(` 打断后启动的 Pi 进程数: ${result.interrupt.spawned}`);
|
|
302
|
+
} else {
|
|
303
|
+
lines.push("");
|
|
304
|
+
lines.push(" 打断:未测(需要一次正在跑工具的轮次)");
|
|
305
|
+
}
|
|
192
306
|
lines.push("");
|
|
193
307
|
lines.push(` catalog: ${result.catalogSource},${result.models} 个模型`);
|
|
194
308
|
|