@evandu/super-pi 0.1.133 → 0.1.135
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/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +6 -6
- package/dist/client/assets/{CodeMirrorEditor-CZZfGD0U.js → CodeMirrorEditor-D6awRUZO.js} +2 -2
- package/dist/client/assets/{dist-azDN8a65.js → dist-1jSyapdW.js} +1 -1
- package/dist/client/assets/{dist-CE5BnwJa.js → dist-BGgidsaF.js} +1 -1
- package/dist/client/assets/{dist-DOYWHMIq.js → dist-BYMYC_2I.js} +1 -1
- package/dist/client/assets/{dist-CYD2SqmG.js → dist-BkVaPA_q.js} +1 -1
- package/dist/client/assets/{dist-CxsunljU.js → dist-BoeM5xW7.js} +1 -1
- package/dist/client/assets/{dist-D4x1XTQb.js → dist-C46474ys.js} +1 -1
- package/dist/client/assets/{dist-BJinarhP.js → dist-CCkdCH6a.js} +1 -1
- package/dist/client/assets/{dist-DQf5nNxa.js → dist-CNuKauOq.js} +1 -1
- package/dist/client/assets/{dist-CApAttfj.js → dist-CubYGjAk.js} +1 -1
- package/dist/client/assets/{dist-0FH-oFco.js → dist-Cwb-Lb5-.js} +1 -1
- package/dist/client/assets/{dist-CFPXj4ws.js → dist-D0Iw5XQA.js} +1 -1
- package/dist/client/assets/{dist-CUKaDTyO.js → dist-DCiuVvGU.js} +1 -1
- package/dist/client/assets/{dist-B5ruVtUG.js → dist-DHBWUQhM.js} +1 -1
- package/dist/client/assets/{dist-BtTFCNVQ.js → dist-DOwrSiuY.js} +1 -1
- package/dist/client/assets/{dist-4Pts9xPO.js → dist-DeZsNbFx.js} +1 -1
- package/dist/client/assets/{dist-aL2NGji_.js → dist-Dk3e6RfY.js} +1 -1
- package/dist/client/assets/{dist-LFfokYJs.js → dist-DoDmoWTn.js} +1 -1
- package/dist/client/assets/{dist-DbbBgSwL.js → dist-WkVUFX28.js} +1 -1
- package/dist/client/assets/{dist-C2nuBCP4.js → dist-lZUWPQbT.js} +1 -1
- package/dist/client/assets/{dist-DHqqpxo4.js → dist-mPR8uuzk.js} +1 -1
- package/dist/client/assets/{dist-fYp8Xwrx.js → dist-spB2Q2CG.js} +1 -1
- package/dist/client/assets/{dist-BffE00vI.js → dist-yTTCht0J.js} +1 -1
- package/dist/client/assets/{index-YEN4ysHF.js → index-Dyta-HkQ.js} +37 -37
- package/dist/client/assets/{index-D3MEG8uH.css → index-V93Kzott.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/active-memory.d.ts +1 -0
- package/dist/server/active-memory.js +6 -2
- package/dist/server/application/run-service.d.ts +4 -0
- package/dist/server/application/run-service.js +114 -93
- package/dist/server/application/workspace-service.d.ts +8 -4
- package/dist/server/application/workspace-service.js +11 -10
- package/dist/server/failure-text.d.ts +18 -0
- package/dist/server/failure-text.js +44 -0
- package/dist/server/http-server.js +16 -5
- package/dist/server/index.js +5 -2
- package/dist/server/knowledge-embedding-worker.d.ts +1 -0
- package/dist/server/knowledge-embedding-worker.js +27 -0
- package/dist/server/knowledge-vectors.d.ts +5 -3
- package/dist/server/knowledge-vectors.js +49 -47
- package/dist/server/knowledge.d.ts +1 -0
- package/dist/server/knowledge.js +6 -2
- package/dist/server/persistence/sqlite-state-repository.d.ts +0 -1
- package/dist/server/persistence/sqlite-state-repository.js +37 -1
- package/dist/server/providers.d.ts +13 -0
- package/dist/server/providers.js +27 -2
- package/dist/server/runtime.js +46 -0
- package/dist/server/store.d.ts +61 -0
- package/dist/server/store.js +112 -14
- package/dist/server/types.d.ts +10 -0
- package/dist/server/ws-server.d.ts +3 -0
- package/dist/server/ws-server.js +35 -6
- package/package.json +16 -2
package/dist/server/store.js
CHANGED
|
@@ -11,6 +11,19 @@ function id(prefix) { return `${prefix}_${crypto.randomUUID()}`; }
|
|
|
11
11
|
* disk, or a failed write would leave a conversation whose model context is silently empty. The runtime is
|
|
12
12
|
* therefore handed the id and the store is handed the same one back. See `ForkRequest.forkedConversationId`. */
|
|
13
13
|
export function newConversationId() { return id("conversation"); }
|
|
14
|
+
/**
|
|
15
|
+
* ONE FOLDER, ONE KEY.
|
|
16
|
+
*
|
|
17
|
+
* The same directory reaches the app spelled several ways - `F:\code\app` and `F:/code/app/` and
|
|
18
|
+
* `f:\code\app` are one folder - and two workspaces for one folder are one project split in half.
|
|
19
|
+
* The key folds the separators and the trailing slash away, and folds case away only where the
|
|
20
|
+
* filesystem folds it too: Windows and macOS treat `F:\Code` and `f:\code` as one directory, Linux
|
|
21
|
+
* does not, and pretending otherwise would merge two real projects into one.
|
|
22
|
+
*/
|
|
23
|
+
export function workspacePathKey(path) {
|
|
24
|
+
const folded = path.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
25
|
+
return process.platform === "linux" ? folded : folded.toLowerCase();
|
|
26
|
+
}
|
|
14
27
|
function now() { return new Date().toISOString(); }
|
|
15
28
|
const DEFAULT_CONVERSATION_TITLE = "\u65b0\u4f1a\u8bdd";
|
|
16
29
|
/** Whether a value survives a `new Date()` round-trip. `createdAt` is typed string, but state.json only
|
|
@@ -735,6 +748,35 @@ export class Store {
|
|
|
735
748
|
console.warn(`[super-pi latency] phase=state.snapshot bytes=${JSON.stringify(value).length} cloneMs=${Math.round(performance.now() - startedAt)}`);
|
|
736
749
|
return value;
|
|
737
750
|
}
|
|
751
|
+
/** Cheap scalar/entity reads for hot paths. Never expose the live mutable state object. */
|
|
752
|
+
hasWorkspace(workspaceId) {
|
|
753
|
+
return this.state.workspaces.some((workspace) => workspace.id === workspaceId);
|
|
754
|
+
}
|
|
755
|
+
conversationStatus(conversationId) {
|
|
756
|
+
return this.state.conversations.find((conversation) => conversation.id === conversationId)?.status;
|
|
757
|
+
}
|
|
758
|
+
question(conversationId, messageId, index) {
|
|
759
|
+
const question = this.state.conversations.find((conversation) => conversation.id === conversationId)
|
|
760
|
+
?.messages.find((message) => message.id === messageId)?.toolCalls?.[index]?.question;
|
|
761
|
+
return question ? structuredClone(question) : undefined;
|
|
762
|
+
}
|
|
763
|
+
pendingQuestion(conversationId) {
|
|
764
|
+
const conversation = this.state.conversations.find((item) => item.id === conversationId);
|
|
765
|
+
if (!conversation)
|
|
766
|
+
return undefined;
|
|
767
|
+
for (let messageIndex = conversation.messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
768
|
+
const message = conversation.messages[messageIndex];
|
|
769
|
+
if (message.role !== "assistant")
|
|
770
|
+
continue;
|
|
771
|
+
for (let index = (message.toolCalls?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
772
|
+
const question = message.toolCalls?.[index]?.question;
|
|
773
|
+
if (message.toolCalls?.[index]?.label === "question" && question?.status === "pending") {
|
|
774
|
+
return { messageId: message.id, index, question: structuredClone(question) };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
738
780
|
/**
|
|
739
781
|
* Whether a conversation already holds a message with this id.
|
|
740
782
|
*
|
|
@@ -767,6 +809,17 @@ export class Store {
|
|
|
767
809
|
return undefined;
|
|
768
810
|
return { conversation: structuredClone(conversation), workspace: structuredClone(workspace) };
|
|
769
811
|
}
|
|
812
|
+
/**
|
|
813
|
+
* THE PHYSICAL DIRECTORY IS THE WORKSPACE'S IDENTITY.
|
|
814
|
+
*
|
|
815
|
+
* A workspace is a folder, not a row in a table: two workspaces pointing at the same directory are
|
|
816
|
+
* the same project, and the app used to mint a fresh UUID for each one. That is what broke
|
|
817
|
+
* re-adding a workspace - the new id matched none of the conversations that belonged to that
|
|
818
|
+
* directory, so the history was unreachable (and, before the soft unmount, actually deleted).
|
|
819
|
+
*
|
|
820
|
+
* So the same folder always means the same workspace id, and the id is what the conversations
|
|
821
|
+
* point at.
|
|
822
|
+
*/
|
|
770
823
|
async createWorkspace(name, path) {
|
|
771
824
|
const cleanName = name.trim();
|
|
772
825
|
if (!cleanName || cleanName.length > 80)
|
|
@@ -774,6 +827,19 @@ export class Store {
|
|
|
774
827
|
const cleanPath = path?.trim() || process.cwd();
|
|
775
828
|
if (cleanPath.length > 500)
|
|
776
829
|
throw new Error("工作区路径太长了。");
|
|
830
|
+
// THE SAME FOLDER IS THE SAME WORKSPACE. Adding a directory that is already here re-opens it
|
|
831
|
+
// instead of forking a second one: the id stays, the conversations stay attached to it, and the
|
|
832
|
+
// name is left alone (renaming is an explicit action elsewhere, and silently renaming a
|
|
833
|
+
// workspace because someone typed a name into the add dialog would be its own surprise).
|
|
834
|
+
const existing = this.state.workspaces.find((item) => workspacePathKey(item.path) === workspacePathKey(cleanPath));
|
|
835
|
+
if (existing) {
|
|
836
|
+
if (existing.closedAt) {
|
|
837
|
+
delete existing.closedAt;
|
|
838
|
+
existing.updatedAt = now();
|
|
839
|
+
await this.persist();
|
|
840
|
+
}
|
|
841
|
+
return structuredClone(existing);
|
|
842
|
+
}
|
|
777
843
|
const workspace = { id: id("workspace"), name: cleanName, path: cleanPath, updatedAt: now() };
|
|
778
844
|
this.state.workspaces.push(workspace);
|
|
779
845
|
await this.persist();
|
|
@@ -830,23 +896,55 @@ export class Store {
|
|
|
830
896
|
await this.persist();
|
|
831
897
|
return structuredClone(workspace);
|
|
832
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* TAKES A WORKSPACE OUT OF THE SIDEBAR. IT DOES NOT DELETE ANYTHING.
|
|
901
|
+
*
|
|
902
|
+
* This used to cascade: the workspace row AND every conversation that pointed at it were erased
|
|
903
|
+
* from the state, in one persist. That made the most natural thing a user could do - remove a
|
|
904
|
+
* workspace to tidy the sidebar, then add the same folder back - silently destroy the history of
|
|
905
|
+
* that project, and there was no way to get it back.
|
|
906
|
+
*
|
|
907
|
+
* So the removal is a SOFT UNMOUNT, the same shape as a session's archive: the workspace row
|
|
908
|
+
* stays (with `closedAt` on it), the conversations stay, the uploads stay, and nothing is
|
|
909
|
+
* broadcast for either (see `clientState`). Adding the same folder again re-opens the very same
|
|
910
|
+
* workspace - same id, same conversations - which is the whole point of the physical path being
|
|
911
|
+
* the workspace's identity.
|
|
912
|
+
*
|
|
913
|
+
* The returned list is the conversations that were STOPPED, not deleted: a turn that was still
|
|
914
|
+
* running under a workspace that just left the sidebar cannot keep burning a model session
|
|
915
|
+
* behind the user's back, and a pending question would otherwise wait for an answer that the
|
|
916
|
+
* sidebar can no longer reach.
|
|
917
|
+
*/
|
|
833
918
|
async deleteWorkspace(workspaceId) {
|
|
834
|
-
|
|
919
|
+
const workspace = this.state.workspaces.find((item) => item.id === workspaceId);
|
|
920
|
+
if (!workspace)
|
|
835
921
|
throw new Error("找不到这个工作区。");
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
// both together. A waiting message needs no sweep of its own any more - it is a message inside one of
|
|
841
|
-
// the conversations being removed, so it goes with its container, which is the whole reason it is a
|
|
842
|
-
// better shape than an entry keyed by conversation id in a separate array: that array needed this
|
|
843
|
-
// cascade to avoid naming a conversation that no longer exists, and could not have been trusted to
|
|
844
|
-
// get it right on its own.
|
|
845
|
-
const doomed = new Set(this.state.conversations.filter((item) => item.workspaceId === workspaceId).map((item) => item.id));
|
|
846
|
-
this.state.workspaces = this.state.workspaces.filter((item) => item.id !== workspaceId);
|
|
847
|
-
this.state.conversations = this.state.conversations.filter((item) => item.workspaceId !== workspaceId);
|
|
922
|
+
if (workspace.closedAt)
|
|
923
|
+
return [];
|
|
924
|
+
workspace.closedAt = now();
|
|
925
|
+
workspace.updatedAt = workspace.closedAt;
|
|
848
926
|
await this.persist();
|
|
849
|
-
return [
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* THE STATE THE CLIENT IS ALLOWED TO SEE.
|
|
931
|
+
*
|
|
932
|
+
* `snapshot()` is the truth of what the store HOLDS; this is the truth of what is ON SCREEN. The
|
|
933
|
+
* difference is the closed workspaces: one that was removed from the sidebar is still in the store
|
|
934
|
+
* - that is exactly why re-adding the folder brings its history back - but it is not part of the
|
|
935
|
+
* app any more, so neither it nor its conversations are broadcast.
|
|
936
|
+
*
|
|
937
|
+
* Filtering HERE, in one place, rather than in every reader, is what keeps the client dumb: the
|
|
938
|
+
* sidebar renders `state.workspaces` and never has to know that a closed workspace exists.
|
|
939
|
+
*/
|
|
940
|
+
clientState() {
|
|
941
|
+
const state = this.snapshot();
|
|
942
|
+
const closed = new Set(state.workspaces.filter((item) => item.closedAt).map((item) => item.id));
|
|
943
|
+
if (!closed.size)
|
|
944
|
+
return state;
|
|
945
|
+
state.workspaces = state.workspaces.filter((item) => !closed.has(item.id));
|
|
946
|
+
state.conversations = state.conversations.filter((item) => !closed.has(item.workspaceId));
|
|
947
|
+
return state;
|
|
850
948
|
}
|
|
851
949
|
async createConversation(workspaceId, configuration) {
|
|
852
950
|
const workspace = this.state.workspaces.find((item) => item.id === workspaceId);
|
package/dist/server/types.d.ts
CHANGED
|
@@ -586,6 +586,12 @@ export type AgentCatalog = {
|
|
|
586
586
|
* the conversation's own field, an existing conversation is therefore immune to later workspace
|
|
587
587
|
* edits, and deleting the workspace default cannot reach back into sessions already born.
|
|
588
588
|
*/
|
|
589
|
+
/**
|
|
590
|
+
* `closedAt` marks a workspace that was REMOVED FROM THE SIDEBAR but is not gone: its conversations
|
|
591
|
+
* are still on disk and still belong to it. The removal is a soft unmount, exactly like a session's
|
|
592
|
+
* `archivedAt` - a state marker, never a deletion - and the marker is what lets the same physical
|
|
593
|
+
* directory be added again later and come back with its history instead of with an empty list.
|
|
594
|
+
*/
|
|
589
595
|
export type Workspace = {
|
|
590
596
|
id: string;
|
|
591
597
|
name: string;
|
|
@@ -594,6 +600,7 @@ export type Workspace = {
|
|
|
594
600
|
defaultModel?: ModelSelection;
|
|
595
601
|
defaultThinkingLevel?: string;
|
|
596
602
|
defaultAgentId?: string;
|
|
603
|
+
closedAt?: string;
|
|
597
604
|
};
|
|
598
605
|
/**
|
|
599
606
|
* Usage accumulated across one sub-agent run. `turns` is the number of finished assistant messages,
|
|
@@ -1469,6 +1476,9 @@ export type ChangedFile = {
|
|
|
1469
1476
|
at: number;
|
|
1470
1477
|
};
|
|
1471
1478
|
export type ServerEvent = {
|
|
1479
|
+
type: "run.stopping";
|
|
1480
|
+
conversationId: string;
|
|
1481
|
+
} | {
|
|
1472
1482
|
type: "tools.updated";
|
|
1473
1483
|
tools: MountedToolSummary[];
|
|
1474
1484
|
}
|
|
@@ -13,6 +13,9 @@ export declare class WsBroadcaster implements EventBroadcaster {
|
|
|
13
13
|
private readonly sockets;
|
|
14
14
|
private readonly coalescedSockets;
|
|
15
15
|
private readonly pendingStreams;
|
|
16
|
+
private cachedState?;
|
|
17
|
+
private cachedStatePayload?;
|
|
18
|
+
private stateFlushScheduled;
|
|
16
19
|
constructor(getState: () => AppState);
|
|
17
20
|
register(socket: WebSocket): void;
|
|
18
21
|
unregister(socket: WebSocket): void;
|
package/dist/server/ws-server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebSocket, WebSocketServer } from "ws";
|
|
2
2
|
import { isAuthorized } from "./http-server.js";
|
|
3
|
+
import { describeThrown, describeThrownWithStack } from "./failure-text.js";
|
|
3
4
|
/**
|
|
4
5
|
* Concrete implementation of EventBroadcaster delivering server events
|
|
5
6
|
* across active WebSocket connections.
|
|
@@ -9,6 +10,9 @@ export class WsBroadcaster {
|
|
|
9
10
|
sockets = new Set();
|
|
10
11
|
coalescedSockets = new Set();
|
|
11
12
|
pendingStreams = new Map();
|
|
13
|
+
cachedState;
|
|
14
|
+
cachedStatePayload;
|
|
15
|
+
stateFlushScheduled = false;
|
|
12
16
|
constructor(getState) {
|
|
13
17
|
this.getState = getState;
|
|
14
18
|
}
|
|
@@ -90,7 +94,23 @@ export class WsBroadcaster {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
broadcastState() {
|
|
93
|
-
|
|
97
|
+
if (this.stateFlushScheduled)
|
|
98
|
+
return;
|
|
99
|
+
this.stateFlushScheduled = true;
|
|
100
|
+
queueMicrotask(() => {
|
|
101
|
+
this.stateFlushScheduled = false;
|
|
102
|
+
this.cachedState = this.getState();
|
|
103
|
+
this.cachedStatePayload = JSON.stringify({ type: "state", state: this.cachedState });
|
|
104
|
+
const payload = this.cachedStatePayload;
|
|
105
|
+
for (const socket of this.sockets) {
|
|
106
|
+
if (socket.readyState !== WebSocket.OPEN)
|
|
107
|
+
continue;
|
|
108
|
+
this.flushStreaming(socket);
|
|
109
|
+
socket.send(payload);
|
|
110
|
+
}
|
|
111
|
+
this.cachedState = undefined;
|
|
112
|
+
this.cachedStatePayload = undefined;
|
|
113
|
+
});
|
|
94
114
|
}
|
|
95
115
|
getStateSnapshot() {
|
|
96
116
|
return this.getState();
|
|
@@ -228,7 +248,8 @@ export class WsServer {
|
|
|
228
248
|
parsed = JSON.parse(raw.toString());
|
|
229
249
|
}
|
|
230
250
|
catch {
|
|
231
|
-
|
|
251
|
+
console.error("Super Pi command error: received a frame that is not valid JSON");
|
|
252
|
+
socket.send(JSON.stringify({ type: "error", message: "收到一条无法解析的命令,已忽略。" }));
|
|
232
253
|
return;
|
|
233
254
|
}
|
|
234
255
|
if (parsed.type === "stream.configure") {
|
|
@@ -264,13 +285,21 @@ export class WsServer {
|
|
|
264
285
|
}
|
|
265
286
|
}
|
|
266
287
|
catch (error) {
|
|
288
|
+
// A FAILED COMMAND WRITES ITSELF DOWN BEFORE IT ANSWERS.
|
|
289
|
+
//
|
|
290
|
+
// The server runs detached with its streams thrown away, so `console.error` here is not
|
|
291
|
+
// decoration: the logger tees it into `<dataDir>/logs/server.log`, which is the only place
|
|
292
|
+
// the stack survives. The browser gets the reason (never a stand-in phrase like
|
|
293
|
+
// `command failed`), because the reader is the one who has to act on it.
|
|
294
|
+
console.error("Super Pi command error:", describeThrownWithStack(error));
|
|
295
|
+
const reason = describeThrown(error);
|
|
267
296
|
if (command?.operationRequestId) {
|
|
268
|
-
socket.send(JSON.stringify({ type: "operation.result", requestId: command.operationRequestId, ok: false, message:
|
|
297
|
+
socket.send(JSON.stringify({ type: "operation.result", requestId: command.operationRequestId, ok: false, message: reason }));
|
|
269
298
|
return;
|
|
270
299
|
}
|
|
271
300
|
socket.send(JSON.stringify({
|
|
272
301
|
type: "error",
|
|
273
|
-
message:
|
|
302
|
+
message: reason || "服务端没有说明这次失败的原因。",
|
|
274
303
|
}));
|
|
275
304
|
}
|
|
276
305
|
finally {
|
|
@@ -284,11 +313,11 @@ export class WsServer {
|
|
|
284
313
|
};
|
|
285
314
|
if (runIndependently)
|
|
286
315
|
void execute().catch((error) => {
|
|
287
|
-
console.
|
|
316
|
+
console.error("Super Pi command dispatch failed:", describeThrownWithStack(error));
|
|
288
317
|
});
|
|
289
318
|
else
|
|
290
319
|
commandQueue = commandQueue.then(execute).catch((error) => {
|
|
291
|
-
console.
|
|
320
|
+
console.error("Super Pi command dispatch failed:", describeThrownWithStack(error));
|
|
292
321
|
});
|
|
293
322
|
});
|
|
294
323
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.135",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -85,10 +85,17 @@
|
|
|
85
85
|
"scripts/workflow-ui.test.mjs",
|
|
86
86
|
"scripts/agent-vis-records.test.mjs",
|
|
87
87
|
"scripts/queue-store.test.mjs",
|
|
88
|
+
"scripts/git-api.test.mjs",
|
|
89
|
+
"scripts/turn-failure-marks-failed.test.mjs",
|
|
90
|
+
"scripts/update-ui-contract.test.mjs",
|
|
91
|
+
"scripts/temp-sweep.test.mjs",
|
|
92
|
+
"scripts/workspace-identity.test.mjs",
|
|
88
93
|
"scripts/followup.test.mjs",
|
|
89
94
|
"scripts/pi-followup-queue.test.mjs",
|
|
90
95
|
"scripts/background-result-delivery.test.mjs",
|
|
96
|
+
"scripts/background-result-one-per-turn.test.mjs",
|
|
91
97
|
"scripts/thinking-status-promotion.test.mjs",
|
|
98
|
+
"scripts/thinking-delivery-once.test.mjs",
|
|
92
99
|
"scripts/question-call-session-path.test.mjs",
|
|
93
100
|
"scripts/withdrawn-message-persistence.test.mjs",
|
|
94
101
|
"scripts/git-write-endpoint-guard.test.mjs",
|
|
@@ -117,6 +124,7 @@
|
|
|
117
124
|
"scripts/scoped-sidebar-tabs.test.mjs",
|
|
118
125
|
"scripts/send-retry.test.mjs",
|
|
119
126
|
"scripts/error-messages.test.mjs",
|
|
127
|
+
"scripts/stop-before-prompt.test.mjs",
|
|
120
128
|
"scripts/git-diff-view.test.mjs",
|
|
121
129
|
"scripts/git-blame.test.mjs",
|
|
122
130
|
"scripts/fork-server.test.mjs",
|
|
@@ -157,7 +165,9 @@
|
|
|
157
165
|
"scripts/feishu.test.mjs",
|
|
158
166
|
"scripts/scheduled-tasks.test.mjs",
|
|
159
167
|
"scripts/scheduled-task-scope.test.mjs",
|
|
160
|
-
"scripts/scheduled-task-tool.test.mjs"
|
|
168
|
+
"scripts/scheduled-task-tool.test.mjs",
|
|
169
|
+
"scripts/run-state-sync.test.mjs",
|
|
170
|
+
"scripts/performance-architecture.test.mjs"
|
|
161
171
|
],
|
|
162
172
|
"check:browser": [
|
|
163
173
|
"scripts/interrupt-browser.test.mjs",
|
|
@@ -178,10 +188,14 @@
|
|
|
178
188
|
"scripts/md-render-browser.test.mjs",
|
|
179
189
|
"scripts/right-sidebar-browser.test.mjs",
|
|
180
190
|
"scripts/rail-workspace-follows-session-browser.test.mjs",
|
|
191
|
+
"scripts/git-tab-browser.test.mjs",
|
|
192
|
+
"scripts/workspace-reopen-browser.test.mjs",
|
|
181
193
|
"scripts/sidebar-tools-browser.test.mjs",
|
|
182
194
|
"scripts/tool-runtime-browser.test.mjs",
|
|
183
195
|
"scripts/session-context-menu-browser.test.mjs",
|
|
184
196
|
"scripts/tool-call-blocks-browser.test.mjs",
|
|
197
|
+
"scripts/assistant-working-indicator-browser.test.mjs",
|
|
198
|
+
"scripts/thinking-first-delta-once-browser.test.mjs",
|
|
185
199
|
"scripts/files-tab-browser.test.mjs",
|
|
186
200
|
"scripts/editor-blame-browser.test.mjs",
|
|
187
201
|
"scripts/file-panel-external-poll-browser.test.mjs",
|