@evandu/super-pi 0.1.13 → 0.1.14

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101828" />
7
7
  <title>Super Pi</title>
8
- <script type="module" crossorigin src="/assets/index-D_K9jjMC.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-C1bVWsCs.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CIxRkYxD.css">
10
10
  </head>
11
11
  <body>
@@ -28,6 +28,8 @@ export declare class ConversationService {
28
28
  private readonly runService;
29
29
  private readonly extensionUiService;
30
30
  private agentsDir;
31
+ /** Conversations whose Pi transcript is being cut RIGHT NOW. Read by the send path, below. */
32
+ private readonly reverting;
31
33
  constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, catalogService: CatalogService, runService: RunService, extensionUiService: ExtensionUiService, agentsDir: string | string[]);
32
34
  setAgentsDir(dir: string | string[]): void;
33
35
  /**
@@ -72,6 +74,9 @@ export declare class ConversationService {
72
74
  content: string;
73
75
  images: import("../types.js").MessageImage[] | undefined;
74
76
  } | undefined>;
77
+ /** Whether a revert is currently cutting this conversation's history. The send path asks. */
78
+ isReverting(conversationId: string): boolean;
79
+ private cutTo;
75
80
  /**
76
81
  * Forks an existing conversation into a new parallel branch, cloning the Pi session.
77
82
  */
@@ -12,6 +12,8 @@ export class ConversationService {
12
12
  runService;
13
13
  extensionUiService;
14
14
  agentsDir;
15
+ /** Conversations whose Pi transcript is being cut RIGHT NOW. Read by the send path, below. */
16
+ reverting = new Set();
15
17
  constructor(store, runtime, subagents, broadcaster, catalogService, runService, extensionUiService, agentsDir) {
16
18
  this.store = store;
17
19
  this.runtime = runtime;
@@ -149,6 +151,28 @@ export class ConversationService {
149
151
  * Pi's transcript and state.json.
150
152
  */
151
153
  async revertMessage(conversationId, messageId, withdraw = false) {
154
+ // CLAIMED FOR THE WHOLE CUT, and released in a `finally` however this ends - a revert that threw
155
+ // halfway must not leave the conversation permanently unable to send.
156
+ //
157
+ // WHY THE CLAIM EXISTS AT ALL. Truncating Pi's transcript is not instantaneous: it spawns a Pi to ask
158
+ // which file the session lives in, and only then writes. A message sent in that window would be
159
+ // appended to a history the model still reads in full - the very turn the person just withdrew would
160
+ // still be in its context, and the new answer would be built on top of it. So the send is what waits,
161
+ // NOT the text coming back: the browser restores the message optimistically and the person can edit
162
+ // it immediately, and what is refused is the next SEND until the cut has landed.
163
+ this.reverting.add(conversationId);
164
+ try {
165
+ return await this.cutTo(conversationId, messageId, withdraw);
166
+ }
167
+ finally {
168
+ this.reverting.delete(conversationId);
169
+ }
170
+ }
171
+ /** Whether a revert is currently cutting this conversation's history. The send path asks. */
172
+ isReverting(conversationId) {
173
+ return this.reverting.has(conversationId);
174
+ }
175
+ async cutTo(conversationId, messageId, withdraw) {
152
176
  let plan = this.store.planRevert(conversationId, messageId, withdraw);
153
177
  let conversation = this.store.snapshot().conversations.find((item) => item.id === conversationId);
154
178
  if (!conversation)
@@ -85,6 +85,12 @@ export class CommandHandler {
85
85
  return;
86
86
  }
87
87
  if (command.type === "message.send") {
88
+ // A send that races a revert would be appended to a history the model is still reading in full - the
89
+ // turn the person just withdrew would still be in its context, and the new answer would be built on
90
+ // top of it. Refusing with a sentence beats accepting a turn built on a cut that has not landed yet.
91
+ if (this.conversationService.isReverting(command.conversationId)) {
92
+ throw new Error("正在撤回上一条消息,Pi 的历史还没截断完成,请等撤回结束后再发送。");
93
+ }
88
94
  await this.runService.enqueueAndRun(command.conversationId, command.content, command.images);
89
95
  return;
90
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",