@evandu/super-pi 0.1.140 → 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 (40) hide show
  1. package/dist/.super-pi-check-inputs.json +2 -2
  2. package/dist/client/.super-pi-client-sources.json +6 -6
  3. package/dist/client/assets/{CodeMirrorEditor-DvZeOLny.js → CodeMirrorEditor-iLUdQy9q.js} +2 -2
  4. package/dist/client/assets/{dist-C_VuSpx8.js → dist-0dCNvYEE.js} +1 -1
  5. package/dist/client/assets/{dist-gyzurUDR.js → dist-B4JqBNQI.js} +1 -1
  6. package/dist/client/assets/{dist-BCUfH4nP.js → dist-B8FbKhnf.js} +1 -1
  7. package/dist/client/assets/{dist-CTkzLBdb.js → dist-BA6EsWzo.js} +1 -1
  8. package/dist/client/assets/{dist-Cb1gxS57.js → dist-Bj2C-QqM.js} +1 -1
  9. package/dist/client/assets/{dist-gBAr9yXF.js → dist-Bt8G7e7e.js} +1 -1
  10. package/dist/client/assets/{dist-DAcS4Kj8.js → dist-CWBm3q4a.js} +1 -1
  11. package/dist/client/assets/{dist-qG53hT1b.js → dist-Cfa_e2VZ.js} +1 -1
  12. package/dist/client/assets/{dist-D7yfnKNC.js → dist-CoTJns5x.js} +1 -1
  13. package/dist/client/assets/{dist-CaUsvSz1.js → dist-CtCiGxne.js} +1 -1
  14. package/dist/client/assets/{dist-DdfW8Gjv.js → dist-DBAoCOnn.js} +1 -1
  15. package/dist/client/assets/{dist-DA8yW4lK.js → dist-DEUl3Yqk.js} +1 -1
  16. package/dist/client/assets/{dist-r4Kq1ABA.js → dist-DHyWJ9kA.js} +1 -1
  17. package/dist/client/assets/{dist-DRgWZqFH.js → dist-DbLEngKN.js} +1 -1
  18. package/dist/client/assets/{dist-C-QmRwbd.js → dist-Df9zcCXs.js} +1 -1
  19. package/dist/client/assets/{dist-CqDSeoUk.js → dist-Dq6y_nK3.js} +1 -1
  20. package/dist/client/assets/{dist-DlQ8PMvv.js → dist-Dxq1s4j3.js} +1 -1
  21. package/dist/client/assets/{dist-3BuyVG3H.js → dist-F634gv7V.js} +1 -1
  22. package/dist/client/assets/{dist-BIIMtU9W.js → dist-JuLMNOUt.js} +1 -1
  23. package/dist/client/assets/{dist-DN0M4DAX.js → dist-Nt-BWmBB.js} +1 -1
  24. package/dist/client/assets/{dist-HJdP1yxN.js → dist-ic-MopV2.js} +1 -1
  25. package/dist/client/assets/{dist-Dvca0LeR.js → dist-rIYPNzbY.js} +1 -1
  26. package/dist/client/assets/{index-CNbh1fL2.js → index-CX5gWDve.js} +13 -13
  27. package/dist/client/assets/{index-DCSFD4VV.css → index-DMhigTiY.css} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/server/agents.d.ts +2 -0
  30. package/dist/server/agents.js +10 -3
  31. package/dist/server/application/conversation-service.d.ts +17 -2
  32. package/dist/server/application/conversation-service.js +42 -8
  33. package/dist/server/application/run-service.d.ts +1 -0
  34. package/dist/server/application/run-service.js +121 -20
  35. package/dist/server/application/session-orchestrator-service.js +8 -1
  36. package/dist/server/command-handler.js +4 -0
  37. package/dist/server/store.d.ts +22 -1
  38. package/dist/server/store.js +108 -3
  39. package/dist/server/types.d.ts +36 -1
  40. package/package.json +325 -315
@@ -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,13 +1086,55 @@ 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
+ }
1095
+ // THE SESSION TREE'S EDGE, WRITTEN AT BIRTH. Both fields describe how this conversation CAME TO
1096
+ // BE, exactly like modelNotice above, so they are set once here and never rewritten: the tree
1097
+ // the sidebar draws after a restart is the tree that was true when each session was opened.
1098
+ // Written only when present, so a conversation created without them (the user's own "+") reads
1099
+ // as a top-level one on every code path, including every state.json from before this field.
1100
+ if (configuration.parentConversationId)
1101
+ conversation.parentConversationId = configuration.parentConversationId;
1102
+ if (configuration.origin)
1103
+ conversation.origin = configuration.origin;
1089
1104
  }
1090
1105
  this.state.conversations.unshift(conversation);
1091
1106
  await this.persist();
1092
1107
  return structuredClone(conversation);
1093
1108
  }
1109
+ /**
1110
+ * Records (or clears) one conversation as collapsed in the sidebar's session tree. The list is a
1111
+ * SET in array clothing - order means nothing, duplicates must not accumulate - and it follows the
1112
+ * assign -> persist -> rollback pattern `setTerminalShell` established: the in-memory value is the
1113
+ * previous value again the moment persistence fails, so a rejected write can never leave the
1114
+ * screen and the disk disagreeing.
1115
+ */
1116
+ async setConversationCollapsed(conversationId, collapsed) {
1117
+ const previous = this.state.collapsedConversationIds;
1118
+ const next = new Set(previous ?? []);
1119
+ if (collapsed)
1120
+ next.add(conversationId);
1121
+ else
1122
+ next.delete(conversationId);
1123
+ if (next.size === 0)
1124
+ delete this.state.collapsedConversationIds;
1125
+ else
1126
+ this.state.collapsedConversationIds = [...next];
1127
+ try {
1128
+ await this.persist();
1129
+ }
1130
+ catch (error) {
1131
+ if (previous === undefined)
1132
+ delete this.state.collapsedConversationIds;
1133
+ else
1134
+ this.state.collapsedConversationIds = previous;
1135
+ throw error;
1136
+ }
1137
+ }
1094
1138
  /**
1095
1139
  * Copies a conversation into a NEW one, under an id the caller has already minted.
1096
1140
  *
@@ -1194,6 +1238,67 @@ export class Store {
1194
1238
  delete conversation.archivedAt;
1195
1239
  await this.persistMetadata(conversationId);
1196
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
+ }
1197
1302
  /**
1198
1303
  * Rename edits the title and NOTHING else. In particular `updatedAt` is deliberately not touched,
1199
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;
@@ -516,6 +519,21 @@ export type Conversation = {
516
519
  * deleted agent degrades to "no role" instead of leaving a stale snapshot in state.json.
517
520
  */
518
521
  agentId?: string;
522
+ /**
523
+ * The conversation that opened this one, when an orchestrator agent created it. Absent for a
524
+ * conversation the user started. This is the session tree's edge, and it PERSISTS: the sidebar
525
+ * draws the hierarchy from it after a restart, which a server-side in-memory ledger cannot do.
526
+ * Absence also reads correctly on every state.json written before this field existed - no parent
527
+ * recorded simply means "top level", which is what those conversations genuinely are.
528
+ */
529
+ parentConversationId?: string;
530
+ /**
531
+ * Who opened this conversation. "user" for one the user started, "orchestrator" for one an agent
532
+ * created. Kept beside the parent id because a conversation can be an orchestrator's without
533
+ * having a parent (the top-level orchestrator), and because the context menu's "stop the session
534
+ * AND its queue" needs to know a queue exists without polling the orchestrator's ledger.
535
+ */
536
+ origin?: "user" | "orchestrator";
519
537
  };
520
538
  /**
521
539
  * One parsed agent definition, as read from an `agents/*.md` file. `prompt` is the file body and is the
@@ -863,7 +881,13 @@ export type AppState = {
863
881
  todosByConversation?: Record<string, TodoItem[]>;
864
882
  workflows?: WorkflowRun[];
865
883
  scheduledTasks?: ScheduledTask[];
866
- customAgentDirs?: string[];
884
+ customAgentDirs?: string[]; /**
885
+ * Which conversations the reader has collapsed in the sidebar's session tree, by conversation id.
886
+ * Server-side so the tree looks the same after a restart and on another machine - the sidebar is a
887
+ * view of the PRODUCT's state, not of one browser's session. Absent (older state.json) means
888
+ * "nothing collapsed", which is the correct reading of a preference that did not exist yet.
889
+ */
890
+ collapsedConversationIds?: string[];
867
891
  };
868
892
  export type TodoItem = {
869
893
  id?: string;
@@ -1158,6 +1182,17 @@ export type ClientCommandBody =
1158
1182
  } | {
1159
1183
  type: "conversation.unarchive";
1160
1184
  conversationId: string;
1185
+ }
1186
+ /**
1187
+ * Collapses or expands one conversation's whole subtree in the sidebar's session tree. The state
1188
+ * lives SERVER-SIDE (`AppState.collapsedConversationIds`), not in the browser: the tree must look
1189
+ * the same after a restart and on another machine, and a bare WS client must not be able to leave
1190
+ * the server's truth and the screen's truth disagreeing about a preference the product owns.
1191
+ */
1192
+ | {
1193
+ type: "conversation.collapse";
1194
+ conversationId: string;
1195
+ collapsed: boolean;
1161
1196
  } | {
1162
1197
  type: "conversation.rename";
1163
1198
  conversationId: string;