@evandu/super-pi 0.1.109 → 0.1.111

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.
Files changed (31) hide show
  1. package/dist/.super-pi-check-inputs.json +2 -2
  2. package/dist/client/.super-pi-client-sources.json +7 -5
  3. package/dist/client/assets/CodeMirrorEditor-CaF1aTpR.js +2 -0
  4. package/dist/client/assets/dist-BG1mkRxC.js +1 -0
  5. package/dist/client/assets/dist-BjrDHYro.js +6 -0
  6. package/dist/client/assets/dist-Bl_iPEld.js +1 -0
  7. package/dist/client/assets/dist-Bv-QAejS.js +1 -0
  8. package/dist/client/assets/dist-BydrWuS9.js +1 -0
  9. package/dist/client/assets/dist-C2JgfsQ0.js +1 -0
  10. package/dist/client/assets/dist-C9gmrpfb.js +3 -0
  11. package/dist/client/assets/dist-CJHSsi_2.js +2 -0
  12. package/dist/client/assets/dist-CKziNS6e.js +23 -0
  13. package/dist/client/assets/dist-CPo4IuhO.js +1 -0
  14. package/dist/client/assets/dist-CbkXScEJ.js +5 -0
  15. package/dist/client/assets/dist-DnwnynHW.js +11 -0
  16. package/dist/client/assets/dist-aC6cFQVF.js +1 -0
  17. package/dist/client/assets/dist-f78PQc3f.js +9 -0
  18. package/dist/client/assets/dist-jDX3NY00.js +7 -0
  19. package/dist/client/assets/dist-o-3gMx5l.js +2 -0
  20. package/dist/client/assets/index-CME3BwBz.js +99 -0
  21. package/dist/client/assets/index-CauO59H-.css +1 -0
  22. package/dist/client/index.html +2 -2
  23. package/dist/server/application/run-service.d.ts +6 -0
  24. package/dist/server/application/run-service.js +29 -6
  25. package/dist/server/persistence/sqlite-state-repository.js +51 -14
  26. package/dist/server/store.d.ts +25 -0
  27. package/dist/server/store.js +32 -0
  28. package/dist/server/ws-server.js +11 -1
  29. package/package.json +15 -1
  30. package/dist/client/assets/index-9JDLS_WZ.js +0 -104
  31. package/dist/client/assets/index-BiqwIwav.css +0 -1
@@ -91,6 +91,31 @@ export declare class Store {
91
91
  */
92
92
  setDefaultModel(model?: ModelSelection): Promise<void>;
93
93
  snapshot(): AppState;
94
+ /**
95
+ * Whether a conversation already holds a message with this id.
96
+ *
97
+ * A LIVE read with no clone, because its one caller is the send path's duplicate check: a resend
98
+ * that arrives within milliseconds of the original must be answered without paying for a full
99
+ * copy of every conversation in the app. The answer is a boolean and the message is not handed
100
+ * out, so nothing can mutate the store through it.
101
+ */
102
+ hasMessage(conversationId: string, messageId: string): boolean;
103
+ /**
104
+ * One conversation and its workspace, as DETACHED copies, without cloning the whole state.
105
+ *
106
+ * The alternative - what the run path used to do - was two `snapshot()` calls, and `snapshot()`
107
+ * deep-clones every conversation, every message and every tool result in the app. That is the
108
+ * shape of the bug it fixes: reading one conversation's model cost O(entire state), and it sat on
109
+ * the path between a click on 发送 and the frame that tells the browser a turn has started.
110
+ *
111
+ * The two objects are still CLONES, because every caller of this is allowed to keep them after the
112
+ * store has moved on (a streamed turn holds its conversation for minutes), and handing out live
113
+ * references would turn the first such caller into a corruption bug that only shows up under load.
114
+ */
115
+ turnContext(conversationId: string): {
116
+ conversation: Conversation;
117
+ workspace: Workspace;
118
+ } | undefined;
94
119
  createWorkspace(name: string, path?: string): Promise<Workspace>;
95
120
  renameWorkspace(workspaceId: string, name: string): Promise<Workspace>;
96
121
  /**
@@ -684,6 +684,38 @@ export class Store {
684
684
  }
685
685
  }
686
686
  snapshot() { return structuredClone(this.state); }
687
+ /**
688
+ * Whether a conversation already holds a message with this id.
689
+ *
690
+ * A LIVE read with no clone, because its one caller is the send path's duplicate check: a resend
691
+ * that arrives within milliseconds of the original must be answered without paying for a full
692
+ * copy of every conversation in the app. The answer is a boolean and the message is not handed
693
+ * out, so nothing can mutate the store through it.
694
+ */
695
+ hasMessage(conversationId, messageId) {
696
+ return Boolean(this.state.conversations.find((item) => item.id === conversationId)?.messages.some((message) => message.id === messageId));
697
+ }
698
+ /**
699
+ * One conversation and its workspace, as DETACHED copies, without cloning the whole state.
700
+ *
701
+ * The alternative - what the run path used to do - was two `snapshot()` calls, and `snapshot()`
702
+ * deep-clones every conversation, every message and every tool result in the app. That is the
703
+ * shape of the bug it fixes: reading one conversation's model cost O(entire state), and it sat on
704
+ * the path between a click on 发送 and the frame that tells the browser a turn has started.
705
+ *
706
+ * The two objects are still CLONES, because every caller of this is allowed to keep them after the
707
+ * store has moved on (a streamed turn holds its conversation for minutes), and handing out live
708
+ * references would turn the first such caller into a corruption bug that only shows up under load.
709
+ */
710
+ turnContext(conversationId) {
711
+ const conversation = this.state.conversations.find((item) => item.id === conversationId);
712
+ if (!conversation)
713
+ return undefined;
714
+ const workspace = this.state.workspaces.find((item) => item.id === conversation.workspaceId);
715
+ if (!workspace)
716
+ return undefined;
717
+ return { conversation: structuredClone(conversation), workspace: structuredClone(workspace) };
718
+ }
687
719
  async createWorkspace(name, path) {
688
720
  const cleanName = name.trim();
689
721
  if (!cleanName || cleanName.length > 80)
@@ -64,6 +64,16 @@ export class WsBroadcaster {
64
64
  }
65
65
  broadcast(event) {
66
66
  const streaming = event.type === "message.delta" || event.type === "message.thinking";
67
+ /**
68
+ * SERIALIZED ONCE, NOT ONCE PER SOCKET. A `state` frame carries the whole application state, so
69
+ * `JSON.stringify` on it is the most expensive thing this method does - and every open tab used
70
+ * to pay for its own copy of the same string. The bytes are identical (no socket-specific field),
71
+ * so the string is built once and handed to every socket.
72
+ *
73
+ * Stream deltas stay per-event: they are coalesced per socket, so there is no single payload to
74
+ * share, and their cost is one small string.
75
+ */
76
+ const payload = streaming ? undefined : JSON.stringify(event);
67
77
  for (const socket of this.sockets) {
68
78
  if (socket.readyState !== WebSocket.OPEN)
69
79
  continue;
@@ -72,7 +82,7 @@ export class WsBroadcaster {
72
82
  else {
73
83
  // A state/tool/lifecycle event is a transcript boundary. Flush its preceding text first.
74
84
  this.flushStreaming(socket);
75
- socket.send(JSON.stringify(event));
85
+ socket.send(payload ?? JSON.stringify(event));
76
86
  }
77
87
  }
78
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -95,6 +95,7 @@
95
95
  "scripts/tool-register-env.test.mjs",
96
96
  "scripts/bg-task-zombie.test.mjs",
97
97
  "scripts/scoped-sidebar-tabs.test.mjs",
98
+ "scripts/send-retry.test.mjs",
98
99
  "scripts/git-diff-view.test.mjs",
99
100
  "scripts/fork-server.test.mjs",
100
101
  "scripts/styles-no-dangling.test.mjs",
@@ -164,6 +165,19 @@
164
165
  "scripts/knowledge-base-browser.test.mjs"
165
166
  ],
166
167
  "dependencies": {
168
+ "@codemirror/commands": "^6.11.1",
169
+ "@codemirror/lang-css": "^6.3.1",
170
+ "@codemirror/lang-go": "^6.0.1",
171
+ "@codemirror/lang-html": "^6.4.12",
172
+ "@codemirror/lang-javascript": "^6.2.5",
173
+ "@codemirror/lang-json": "^6.0.2",
174
+ "@codemirror/lang-markdown": "^6.5.2",
175
+ "@codemirror/lang-python": "^6.2.1",
176
+ "@codemirror/lang-rust": "^6.0.2",
177
+ "@codemirror/language": "^6.13.1",
178
+ "@codemirror/search": "^6.7.2",
179
+ "@codemirror/state": "^6.7.6",
180
+ "@codemirror/view": "^6.43.15",
167
181
  "@earendil-works/pi-coding-agent": "^0.86.1",
168
182
  "@git-diff-view/react": "^0.1.8",
169
183
  "@types/diff": "^5.2.3",