@evandu/super-pi 0.1.141 → 0.1.142

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 (34) hide show
  1. package/dist/.super-pi-check-inputs.json +2 -2
  2. package/dist/client/.super-pi-client-sources.json +5 -6
  3. package/dist/client/assets/{CodeMirrorEditor-Dm5IDKy9.js → CodeMirrorEditor-iLUdQy9q.js} +2 -2
  4. package/dist/client/assets/{dist-DtO-7WEZ.js → dist-0dCNvYEE.js} +1 -1
  5. package/dist/client/assets/{dist-DQWPdnen.js → dist-B4JqBNQI.js} +1 -1
  6. package/dist/client/assets/{dist-D0-9TI_k.js → dist-B8FbKhnf.js} +1 -1
  7. package/dist/client/assets/{dist-4baaMdqn.js → dist-BA6EsWzo.js} +1 -1
  8. package/dist/client/assets/{dist-BGaKp7BG.js → dist-Bj2C-QqM.js} +1 -1
  9. package/dist/client/assets/{dist-LCDxbTTI.js → dist-Bt8G7e7e.js} +1 -1
  10. package/dist/client/assets/{dist-B-edBMDa.js → dist-CWBm3q4a.js} +1 -1
  11. package/dist/client/assets/{dist-D7YZUqfA.js → dist-Cfa_e2VZ.js} +1 -1
  12. package/dist/client/assets/{dist-GAPsnCM8.js → dist-CoTJns5x.js} +1 -1
  13. package/dist/client/assets/{dist-BqXU6S8h.js → dist-CtCiGxne.js} +1 -1
  14. package/dist/client/assets/{dist-D0eIQjoI.js → dist-DBAoCOnn.js} +1 -1
  15. package/dist/client/assets/{dist-CNgQgdyS.js → dist-DEUl3Yqk.js} +1 -1
  16. package/dist/client/assets/{dist-F7KOq8Xm.js → dist-DHyWJ9kA.js} +1 -1
  17. package/dist/client/assets/{dist-BMbwbGH9.js → dist-DbLEngKN.js} +1 -1
  18. package/dist/client/assets/{dist-CMDW84Eh.js → dist-Df9zcCXs.js} +1 -1
  19. package/dist/client/assets/{dist-CN1j5LO2.js → dist-Dq6y_nK3.js} +1 -1
  20. package/dist/client/assets/{dist-BvCNiV_z.js → dist-Dxq1s4j3.js} +1 -1
  21. package/dist/client/assets/{dist-C7UQGfmz.js → dist-F634gv7V.js} +1 -1
  22. package/dist/client/assets/{dist-PQZBepmy.js → dist-JuLMNOUt.js} +1 -1
  23. package/dist/client/assets/{dist-CSDLiQKt.js → dist-Nt-BWmBB.js} +1 -1
  24. package/dist/client/assets/{dist-DWj0wg8M.js → dist-ic-MopV2.js} +1 -1
  25. package/dist/client/assets/{dist-M3BFsjal.js → dist-rIYPNzbY.js} +1 -1
  26. package/dist/client/assets/{index-DHsjaI7m.js → index-CX5gWDve.js} +12 -12
  27. package/dist/client/assets/{index-BJIfLtDs.css → index-DMhigTiY.css} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/server/application/conversation-service.d.ts +2 -1
  30. package/dist/server/application/conversation-service.js +25 -7
  31. package/dist/server/store.d.ts +12 -1
  32. package/dist/server/store.js +70 -3
  33. package/dist/server/types.d.ts +3 -0
  34. package/package.json +325 -316
@@ -8,8 +8,8 @@
8
8
  <link rel="alternate icon" href="/favicon.ico" />
9
9
  <link rel="apple-touch-icon" href="/favicon.svg" />
10
10
  <title>Super Pi</title>
11
- <script type="module" crossorigin src="/assets/index-DHsjaI7m.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-BJIfLtDs.css">
11
+ <script type="module" crossorigin src="/assets/index-CX5gWDve.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-DMhigTiY.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -74,7 +74,8 @@ export declare class ConversationService {
74
74
  */
75
75
  archiveConversation(conversationId: string): Promise<void>;
76
76
  /**
77
- * Unarchives a conversation.
77
+ * Unarchives an archived tree root and every archived descendant. Descendants that are
78
+ * already active need no write; they are already in the live tree this restore rejoins.
78
79
  */
79
80
  unarchiveConversation(conversationId: string): Promise<void>;
80
81
  /**
@@ -1,5 +1,11 @@
1
1
  import { assertModelAllowed } from "../model-filter.js";
2
2
  import { newConversationId } from "../store.js";
3
+ function isMissingPrimaryAgent(error, agentId) {
4
+ const message = error instanceof Error ? error.message : String(error);
5
+ const id = JSON.stringify(agentId);
6
+ return message.includes(`Agent ${id} 已失效或不是可用的 primary agent`)
7
+ || message.includes(`Agent ${id} 不存在或不是可用的 primary agent`);
8
+ }
3
9
  /**
4
10
  * Service managing conversation lifecycle: creation, selection, archiving,
5
11
  * model/thinking/agent configuration, message reversion, and session forking.
@@ -104,12 +110,23 @@ export class ConversationService {
104
110
  thinkingLevel = undefined;
105
111
  }
106
112
  }
107
- const resolved = workspace.defaultAgentId
108
- ? await this.resolveAgentConfiguration({ id: workspaceId, model, thinkingLevel }, workspace.defaultAgentId, workspaceId)
109
- : { model, thinkingLevel };
113
+ let agentNotice;
114
+ let resolved = { model, thinkingLevel };
115
+ if (workspace.defaultAgentId) {
116
+ try {
117
+ resolved = await this.resolveAgentConfiguration({ id: workspaceId, model, thinkingLevel }, workspace.defaultAgentId, workspaceId);
118
+ }
119
+ catch (error) {
120
+ // Only an absent definition is downgraded. A present agent whose model, thinking level or
121
+ // workspace rules are invalid must still refuse rather than silently dropping its configuration.
122
+ if (!isMissingPrimaryAgent(error, workspace.defaultAgentId))
123
+ throw error;
124
+ agentNotice = `你设的默认 Agent「${workspace.defaultAgentId}」已经不存在了,这个会话改用 Pi 默认 Agent。请在工作区设置里重新选择默认 Agent。`;
125
+ }
126
+ }
110
127
  // `null` is intentional here: Store uses it to remove a workspace value that it
111
128
  // would otherwise have copied before applying the resolved configuration.
112
- const defaults = { ...resolved, model: resolved.model ?? null, thinkingLevel: resolved.thinkingLevel ?? null, ...(modelNotice ? { modelNotice } : {}), ...(parentage ? { parentConversationId: parentage.parentConversationId, origin: parentage.origin } : {}) };
129
+ const defaults = { ...resolved, agentId: resolved.agentId ?? null, model: resolved.model ?? null, thinkingLevel: resolved.thinkingLevel ?? null, ...(modelNotice ? { modelNotice } : {}), ...(agentNotice ? { agentNotice } : {}), ...(parentage ? { parentConversationId: parentage.parentConversationId, origin: parentage.origin } : {}) };
113
130
  const created = await this.store.createConversation(workspaceId, defaults);
114
131
  this.broadcaster.broadcastState();
115
132
  return created.id;
@@ -169,14 +186,15 @@ export class ConversationService {
169
186
  */
170
187
  async archiveConversation(conversationId) {
171
188
  this.runService.reviewMemoryBoundary(conversationId);
172
- await this.store.archiveConversation(conversationId);
189
+ await this.store.archiveConversationTree(conversationId);
173
190
  this.broadcaster.broadcastState();
174
191
  }
175
192
  /**
176
- * Unarchives a conversation.
193
+ * Unarchives an archived tree root and every archived descendant. Descendants that are
194
+ * already active need no write; they are already in the live tree this restore rejoins.
177
195
  */
178
196
  async unarchiveConversation(conversationId) {
179
- await this.store.unarchiveConversation(conversationId);
197
+ await this.store.unarchiveConversationTree(conversationId);
180
198
  this.broadcaster.broadcastState();
181
199
  }
182
200
  /**
@@ -266,10 +266,11 @@ export declare class Store {
266
266
  conversationId?: string;
267
267
  }): AppState;
268
268
  createConversation(workspaceId: string, configuration?: {
269
- agentId?: string;
269
+ agentId?: string | null;
270
270
  model?: ModelSelection | null;
271
271
  thinkingLevel?: string | null;
272
272
  modelNotice?: string;
273
+ agentNotice?: string;
273
274
  parentConversationId?: string;
274
275
  origin?: "user" | "orchestrator";
275
276
  }): Promise<Conversation>;
@@ -341,6 +342,16 @@ export declare class Store {
341
342
  /** Restoring deletes the key rather than setting it to null, so a fully restored conversation serializes
342
343
  * byte-identically to one that was never archived. `delete` is legal here because the field is optional. */
343
344
  unarchiveConversation(conversationId: string): Promise<void>;
345
+ /**
346
+ * Archives one whole tree as one state write. Only a conversation with no existing parent may
347
+ * start it; an archived parent is still a parent, so its active descendants cannot be archived
348
+ * independently. All running checks happen before the first mutation, making rejection all-or-none.
349
+ */
350
+ archiveConversationTree(conversationId: string): Promise<void>;
351
+ /** Restores one archived tree in one write; active descendants are already restored by definition. */
352
+ unarchiveConversationTree(conversationId: string): Promise<void>;
353
+ /** Pre-order traversal with cycle protection; archived nodes are traversed, not treated as leaves. */
354
+ private conversationSubtree;
344
355
  /**
345
356
  * Rename edits the title and NOTHING else. In particular `updatedAt` is deliberately not touched,
346
357
  * for exactly the reason archive is: `updatedAt` is the sidebar's recency key, so bumping it would
@@ -1060,7 +1060,7 @@ export class Store {
1060
1060
  // here and never consulted again: the session picker edits the conversation's own fields, so an
1061
1061
  // existing conversation is untouched by later workspace settings, and a workspace edit never
1062
1062
  // rewrites history. Field by field, never a spread, for the same reason setConversationModel
1063
- // narrows: the type declares three fields, so three fields are written.
1063
+ // narrows: every inherited field has an explicit birth override, including null for Pi's default.
1064
1064
  if (workspace.defaultModel)
1065
1065
  conversation.model = { provider: workspace.defaultModel.provider, modelId: workspace.defaultModel.modelId, label: workspace.defaultModel.label };
1066
1066
  if (workspace.defaultThinkingLevel)
@@ -1072,6 +1072,8 @@ export class Store {
1072
1072
  // rewritten: it describes how this conversation came to be, not what is true right now.
1073
1073
  if (configuration.modelNotice !== undefined)
1074
1074
  conversation.modelNotice = configuration.modelNotice;
1075
+ if (configuration.agentNotice !== undefined)
1076
+ conversation.agentNotice = configuration.agentNotice;
1075
1077
  if (configuration.model !== undefined) {
1076
1078
  if (configuration.model)
1077
1079
  conversation.model = { ...configuration.model };
@@ -1084,8 +1086,12 @@ export class Store {
1084
1086
  else
1085
1087
  delete conversation.thinkingLevel;
1086
1088
  }
1087
- if (configuration.agentId)
1088
- conversation.agentId = configuration.agentId;
1089
+ if (configuration.agentId !== undefined) {
1090
+ if (configuration.agentId)
1091
+ conversation.agentId = configuration.agentId;
1092
+ else
1093
+ delete conversation.agentId;
1094
+ }
1089
1095
  // THE SESSION TREE'S EDGE, WRITTEN AT BIRTH. Both fields describe how this conversation CAME TO
1090
1096
  // BE, exactly like modelNotice above, so they are set once here and never rewritten: the tree
1091
1097
  // the sidebar draws after a restart is the tree that was true when each session was opened.
@@ -1232,6 +1238,67 @@ export class Store {
1232
1238
  delete conversation.archivedAt;
1233
1239
  await this.persistMetadata(conversationId);
1234
1240
  }
1241
+ /**
1242
+ * Archives one whole tree as one state write. Only a conversation with no existing parent may
1243
+ * start it; an archived parent is still a parent, so its active descendants cannot be archived
1244
+ * independently. All running checks happen before the first mutation, making rejection all-or-none.
1245
+ */
1246
+ async archiveConversationTree(conversationId) {
1247
+ const root = this.requireConversation(conversationId);
1248
+ if (root.parentConversationId && this.state.conversations.some(item => item.id === root.parentConversationId)) {
1249
+ throw new Error("子会话不能独立归档,请从根会话归档整棵树");
1250
+ }
1251
+ if (root.archivedAt)
1252
+ return;
1253
+ const active = this.conversationSubtree(root).filter(item => !item.archivedAt);
1254
+ const running = active.find(item => item.status === "running");
1255
+ if (running)
1256
+ throw new Error(`会话「${running.title}」正在运行,无法归档整棵树,请先停止运行`);
1257
+ const archivedAt = now();
1258
+ for (const conversation of active)
1259
+ conversation.archivedAt = archivedAt;
1260
+ await this.persist();
1261
+ }
1262
+ /** Restores one archived tree in one write; active descendants are already restored by definition. */
1263
+ async unarchiveConversationTree(conversationId) {
1264
+ const root = this.requireConversation(conversationId);
1265
+ const parent = root.parentConversationId
1266
+ ? this.state.conversations.find(item => item.id === root.parentConversationId)
1267
+ : undefined;
1268
+ if (parent?.archivedAt)
1269
+ throw new Error("归档树中的子会话不能独立恢复,请从归档树的根会话恢复整棵树");
1270
+ if (!root.archivedAt)
1271
+ return;
1272
+ const archived = this.conversationSubtree(root).filter(item => item.archivedAt);
1273
+ for (const conversation of archived)
1274
+ delete conversation.archivedAt;
1275
+ await this.persist();
1276
+ }
1277
+ /** Pre-order traversal with cycle protection; archived nodes are traversed, not treated as leaves. */
1278
+ conversationSubtree(root) {
1279
+ const byParent = new Map();
1280
+ for (const conversation of this.state.conversations) {
1281
+ if (conversation.workspaceId !== root.workspaceId || !conversation.parentConversationId)
1282
+ continue;
1283
+ const siblings = byParent.get(conversation.parentConversationId);
1284
+ if (siblings)
1285
+ siblings.push(conversation);
1286
+ else
1287
+ byParent.set(conversation.parentConversationId, [conversation]);
1288
+ }
1289
+ const result = [];
1290
+ const visited = new Set();
1291
+ const walk = (conversation) => {
1292
+ if (visited.has(conversation.id))
1293
+ return;
1294
+ visited.add(conversation.id);
1295
+ result.push(conversation);
1296
+ for (const child of byParent.get(conversation.id) ?? [])
1297
+ walk(child);
1298
+ };
1299
+ walk(root);
1300
+ return result;
1301
+ }
1235
1302
  /**
1236
1303
  * Rename edits the title and NOTHING else. In particular `updatedAt` is deliberately not touched,
1237
1304
  * for exactly the reason archive is: `updatedAt` is the sidebar's recency key, so bumping it would
@@ -503,6 +503,9 @@ export type Conversation = {
503
503
  * not what is true right now.
504
504
  */
505
505
  modelNotice?: string;
506
+ /** Why this conversation started on Pi's default agent instead of the workspace's deleted default.
507
+ * Written once at birth, like modelNotice, and rendered verbatim. */
508
+ agentNotice?: string;
506
509
  /** Absent means "Pi's own default", which is why a conversation created before the picker existed still works. */
507
510
  model?: ModelSelection;
508
511
  thinkingLevel?: string;