@evandu/super-pi 0.1.145 → 0.1.147

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 (93) hide show
  1. package/agents/super-pi-task/core.ts +74 -2
  2. package/agents/super-pi-task/index.ts +7 -0
  3. package/dist/.super-pi-check-inputs.json +2 -2
  4. package/dist/client/.super-pi-client-sources.json +11 -11
  5. package/dist/client/assets/{CodeMirrorEditor-A9KvbIkm.js → CodeMirrorEditor-DQCjEnxG.js} +2 -2
  6. package/dist/client/assets/{dist-DM4Olwfx.js → dist-1Innu17E.js} +1 -1
  7. package/dist/client/assets/{dist-bIDce5j-.js → dist-1lynFOc5.js} +1 -1
  8. package/dist/client/assets/{dist-BlxwDnWr.js → dist-B1qLZVMK.js} +1 -1
  9. package/dist/client/assets/{dist-BTVbZigv.js → dist-B4pz_xdd.js} +1 -1
  10. package/dist/client/assets/{dist-UJ46aPS3.js → dist-B6KwDpeI.js} +1 -1
  11. package/dist/client/assets/{dist-CK2KFFtT.js → dist-BJVI1Unv.js} +1 -1
  12. package/dist/client/assets/{dist-CRnm1rnO.js → dist-BWvzD8Pe.js} +1 -1
  13. package/dist/client/assets/{dist-DSHM-PAs.js → dist-BayWssd0.js} +1 -1
  14. package/dist/client/assets/{dist-B9DE7sc5.js → dist-Bk_pkBVH.js} +1 -1
  15. package/dist/client/assets/{dist-D-o7iQ8c.js → dist-BpeClAv0.js} +1 -1
  16. package/dist/client/assets/{dist-CQFASxyb.js → dist-BsmJg5PX.js} +1 -1
  17. package/dist/client/assets/{dist-Ds5gfeAi.js → dist-Bx7s4rni.js} +1 -1
  18. package/dist/client/assets/{dist-UPJPMjWv.js → dist-CDBKM9tL.js} +1 -1
  19. package/dist/client/assets/{dist-CIgjifH-.js → dist-CGzXCzCG.js} +1 -1
  20. package/dist/client/assets/{dist-BAiV8I_O.js → dist-CRxCwn-Z.js} +1 -1
  21. package/dist/client/assets/{dist-Br-AuQUG.js → dist-CTDgUnBG.js} +1 -1
  22. package/dist/client/assets/{dist-4JbFTwg2.js → dist-D1VfMWmf.js} +1 -1
  23. package/dist/client/assets/{dist-BYOpvW02.js → dist-DTZLNEci.js} +1 -1
  24. package/dist/client/assets/{dist-CQV5kOCE.js → dist-DuUi29km.js} +1 -1
  25. package/dist/client/assets/{dist-CjszAoXk.js → dist-F3S0t1oM.js} +1 -1
  26. package/dist/client/assets/{dist-2boAf_pN.js → dist-QZ-FAVJV.js} +1 -1
  27. package/dist/client/assets/{dist-B1p4YF9C.js → dist-u0nBg0fZ.js} +1 -1
  28. package/dist/client/assets/index-BMpqviwU.css +1 -0
  29. package/dist/client/assets/index-Dzyp_7sf.js +102 -0
  30. package/dist/client/index.html +2 -2
  31. package/dist/server/active-memory.d.ts +17 -0
  32. package/dist/server/active-memory.js +39 -0
  33. package/dist/server/agents.d.ts +1 -1
  34. package/dist/server/agents.js +3 -1
  35. package/dist/server/application/conversation-service.d.ts +1 -1
  36. package/dist/server/application/conversation-service.js +12 -4
  37. package/dist/server/application/run-service.d.ts +32 -2
  38. package/dist/server/application/run-service.js +296 -40
  39. package/dist/server/application/scheduled-task-service.d.ts +14 -1
  40. package/dist/server/application/scheduled-task-service.js +27 -2
  41. package/dist/server/application/session-orchestrator-service.d.ts +114 -3
  42. package/dist/server/application/session-orchestrator-service.js +406 -35
  43. package/dist/server/application/session-watchdog.d.ts +159 -0
  44. package/dist/server/application/session-watchdog.js +244 -0
  45. package/dist/server/command-handler.js +1 -1
  46. package/dist/server/conversation-usage.d.ts +19 -0
  47. package/dist/server/conversation-usage.js +40 -0
  48. package/dist/server/file-diff.d.ts +3 -0
  49. package/dist/server/file-diff.js +4 -4
  50. package/dist/server/file-index.d.ts +4 -2
  51. package/dist/server/file-index.js +14 -9
  52. package/dist/server/file-name-index.d.ts +5 -2
  53. package/dist/server/file-name-index.js +22 -5
  54. package/dist/server/git.d.ts +3 -3
  55. package/dist/server/git.js +10 -9
  56. package/dist/server/http-server.js +256 -34
  57. package/dist/server/index.js +3 -0
  58. package/dist/server/knowledge-vectors.d.ts +15 -0
  59. package/dist/server/knowledge-vectors.js +118 -37
  60. package/dist/server/knowledge.d.ts +7 -8
  61. package/dist/server/knowledge.js +133 -55
  62. package/dist/server/length-continuation.d.ts +50 -0
  63. package/dist/server/length-continuation.js +55 -0
  64. package/dist/server/model-context-window.d.ts +46 -0
  65. package/dist/server/model-context-window.js +121 -0
  66. package/dist/server/pi-session-events.js +5 -1
  67. package/dist/server/pi-session-pool.d.ts +2 -0
  68. package/dist/server/pi-session-pool.js +16 -0
  69. package/dist/server/pi-session.d.ts +6 -0
  70. package/dist/server/pi-session.js +15 -1
  71. package/dist/server/runtime.d.ts +3 -0
  72. package/dist/server/runtime.js +23 -7
  73. package/dist/server/session-cwd.d.ts +17 -0
  74. package/dist/server/session-cwd.js +43 -0
  75. package/dist/server/session-ledger.d.ts +7 -0
  76. package/dist/server/session-tree-scope.d.ts +125 -0
  77. package/dist/server/session-tree-scope.js +337 -0
  78. package/dist/server/store.d.ts +35 -0
  79. package/dist/server/store.js +87 -2
  80. package/dist/server/task-tool.d.ts +2 -1
  81. package/dist/server/task-tool.js +4 -1
  82. package/dist/server/terminal.d.ts +1 -0
  83. package/dist/server/terminal.js +5 -3
  84. package/dist/server/turn-failure.d.ts +26 -0
  85. package/dist/server/turn-failure.js +67 -1
  86. package/dist/server/types.d.ts +33 -1
  87. package/dist/server/version.d.ts +25 -2
  88. package/dist/server/version.js +98 -26
  89. package/dist/server/ws-server.d.ts +1 -1
  90. package/dist/server/ws-server.js +7 -5
  91. package/package.json +36 -3
  92. package/dist/client/assets/index-BGsP8pt_.js +0 -100
  93. package/dist/client/assets/index-CiIc734l.css +0 -1
@@ -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-BGsP8pt_.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-CiIc734l.css">
11
+ <script type="module" crossorigin src="/assets/index-Dzyp_7sf.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-BMpqviwU.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -77,6 +77,23 @@ export declare class ActiveMemory {
77
77
  score: number;
78
78
  content: string;
79
79
  }[];
80
+ /**
81
+ * Rebuilds one conversation's postings after a rare destructive transcript mutation (revert/delete).
82
+ *
83
+ * WHY THIS EXISTS. The index is a CACHE of the store, and `removeFromIndex` is the surgical half of
84
+ * that contract: the caller tells it which ids LEFT, and exactly those postings go. This is the other
85
+ * half - it rebuilds what is there NOW. A destructive mutation can also change a message's CONTENT
86
+ * (a withdrawn turn replaced by a rewritten one), and a posting that still carries the old text
87
+ * answers a search with a turn the transcript no longer holds. So the rare path pays a full
88
+ * re-tokenize of ONE conversation, and the rest of the workspace stays indexed.
89
+ *
90
+ * THE REMOVAL LOOP WALKS EVERY WORKSPACE, because a posting remembers the conversation it came from:
91
+ * the conversation's terms must stop answering searches wherever they were indexed, not only in the
92
+ * workspace it lives in. The reverse index (`termsByMessage`) is pruned in the same loop, so the two
93
+ * halves of the index cannot disagree about which messages are indexed - a stale entry would point
94
+ * the surgical removal at terms that are no longer there.
95
+ */
96
+ syncConversation(conversation: Conversation): void;
80
97
  private ensureIndex;
81
98
  /**
82
99
  * Drops exactly the given messages from the search index, leaving the rest of the workspace indexed.
@@ -135,6 +135,41 @@ export class ActiveMemory {
135
135
  void id, candidates.set(value.messageId, value);
136
136
  return [...candidates.values()].filter(m => !sessionId || m.conversationId === sessionId).map(m => ({ ...m, score: words.reduce((n, word) => n + (m.content.toLocaleLowerCase().includes(word) ? 1 : 0), 0) })).filter(m => m.score > 0).sort((a, b) => b.score - a.score).slice(0, 6).map(m => ({ ...m, content: m.content.slice(0, 1200) }));
137
137
  }
138
+ /**
139
+ * Rebuilds one conversation's postings after a rare destructive transcript mutation (revert/delete).
140
+ *
141
+ * WHY THIS EXISTS. The index is a CACHE of the store, and `removeFromIndex` is the surgical half of
142
+ * that contract: the caller tells it which ids LEFT, and exactly those postings go. This is the other
143
+ * half - it rebuilds what is there NOW. A destructive mutation can also change a message's CONTENT
144
+ * (a withdrawn turn replaced by a rewritten one), and a posting that still carries the old text
145
+ * answers a search with a turn the transcript no longer holds. So the rare path pays a full
146
+ * re-tokenize of ONE conversation, and the rest of the workspace stays indexed.
147
+ *
148
+ * THE REMOVAL LOOP WALKS EVERY WORKSPACE, because a posting remembers the conversation it came from:
149
+ * the conversation's terms must stop answering searches wherever they were indexed, not only in the
150
+ * workspace it lives in. The reverse index (`termsByMessage`) is pruned in the same loop, so the two
151
+ * halves of the index cannot disagree about which messages are indexed - a stale entry would point
152
+ * the surgical removal at terms that are no longer there.
153
+ */
154
+ syncConversation(conversation) {
155
+ for (const [, terms] of this.inverted) {
156
+ for (const [term, postings] of terms) {
157
+ for (const [messageId, value] of postings) {
158
+ if (value.conversationId === conversation.id) {
159
+ postings.delete(messageId);
160
+ this.indexedMessages.delete(messageId);
161
+ this.termsByMessage.delete(messageId);
162
+ }
163
+ }
164
+ if (!postings.size)
165
+ terms.delete(term);
166
+ }
167
+ }
168
+ if (!this.indexedWorkspaces.has(conversation.workspaceId))
169
+ return;
170
+ for (const message of conversation.messages)
171
+ this.addToIndex(conversation, message);
172
+ }
138
173
  ensureIndex(workspaceId) {
139
174
  if (this.indexedWorkspaces.has(workspaceId))
140
175
  return;
@@ -224,6 +259,10 @@ export class ActiveMemory {
224
259
  terms.set(term, bucket = new Map());
225
260
  bucket.set(message.id, value);
226
261
  }
262
+ // THE MESSAGE IS IN THE INDEX NOW, SO THE SET MUST SAY SO. `completed()` walks this set to find
263
+ // the messages it has never indexed, and a message that is indexed without being marked here is
264
+ // re-tokenized on EVERY turn end - the whole-conversation cost this file exists to avoid.
265
+ this.indexedMessages.add(message.id);
227
266
  }
228
267
  async tool(token, body) {
229
268
  const identity = this.identity(token);
@@ -38,7 +38,7 @@ type Frontmatter = Record<string, unknown>;
38
38
  */
39
39
  export declare const PI_TOOL_NAMES: readonly ["read", "bash", "powershell", "edit", "write", "grep", "find", "ls", "codemode", "tool_search", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
40
40
  /** Tools shipped by this package. Authors must be able to explicitly deny host extras. */
41
- export declare const SUPER_PI_TOOL_NAMES: readonly ["task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register", "memory", "skill_manage", "session_search", "knowledge_search", "knowledge_import", "document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write"];
41
+ export declare const SUPER_PI_TOOL_NAMES: readonly ["task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register", "memory", "skill_manage", "session_search", "session_spawn", "session_status", "session_dispatch", "session_stop", "knowledge_search", "knowledge_import", "document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write"];
42
42
  /**
43
43
  * Whether a name can be a real Pi tool. A name is real if it is one of `PI_TOOL_NAMES`, an MCP
44
44
  * server tool, or a PATTERN.
@@ -39,7 +39,9 @@ export const PI_TOOL_NAMES = [
39
39
  /** Tools shipped by this package. Authors must be able to explicitly deny host extras. */
40
40
  export const SUPER_PI_TOOL_NAMES = [
41
41
  "task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register",
42
- "memory", "skill_manage", "session_search", "knowledge_search", "knowledge_import",
42
+ "memory", "skill_manage", "session_search",
43
+ "session_spawn", "session_status", "session_dispatch", "session_stop",
44
+ "knowledge_search", "knowledge_import",
43
45
  "document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write",
44
46
  ];
45
47
  const KNOWN_TOOL_NAMES = [...PI_TOOL_NAMES, ...SUPER_PI_TOOL_NAMES];
@@ -48,7 +48,7 @@ export declare class ConversationService {
48
48
  createConversation(workspaceId: string, parentage?: {
49
49
  parentConversationId?: string;
50
50
  origin?: "user" | "orchestrator";
51
- }): Promise<string>;
51
+ }, cwd?: string): Promise<string>;
52
52
  /**
53
53
  * Name a conversation from what was said in it. Returns the title; the CALLER applies it, because the
54
54
  * rename is a second command with its own optimistic path and its own failure toast, and a service that
@@ -1,5 +1,6 @@
1
1
  import { assertModelAllowed } from "../model-filter.js";
2
2
  import { newConversationId } from "../store.js";
3
+ import { normalizeSessionCwd, resolveSessionCwd } from "../session-cwd.js";
3
4
  function isMissingPrimaryAgent(error, agentId) {
4
5
  const message = error instanceof Error ? error.message : String(error);
5
6
  const id = JSON.stringify(agentId);
@@ -50,7 +51,7 @@ export class ConversationService {
50
51
  * button and every other caller create a top-level conversation by omission, which is the same
51
52
  * "absence means top level" rule the pre-tree state.json files read under.
52
53
  */
53
- async createConversation(workspaceId, parentage) {
54
+ async createConversation(workspaceId, parentage, cwd) {
54
55
  const workspace = this.store.workspace(workspaceId);
55
56
  if (!workspace)
56
57
  throw new Error("找不到这个工作区。");
@@ -126,7 +127,8 @@ export class ConversationService {
126
127
  }
127
128
  // `null` is intentional here: Store uses it to remove a workspace value that it
128
129
  // would otherwise have copied before applying the resolved configuration.
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 } : {}) };
130
+ const sessionCwd = await normalizeSessionCwd(workspace.path, cwd);
131
+ const defaults = { ...resolved, agentId: resolved.agentId ?? null, model: resolved.model ?? null, thinkingLevel: resolved.thinkingLevel ?? null, ...(sessionCwd ? { cwd: sessionCwd } : {}), ...(modelNotice ? { modelNotice } : {}), ...(agentNotice ? { agentNotice } : {}), ...(parentage ? { parentConversationId: parentage.parentConversationId, origin: parentage.origin } : {}) };
130
132
  const created = await this.store.createConversation(workspaceId, defaults);
131
133
  this.broadcaster.broadcastState();
132
134
  return created.id;
@@ -422,7 +424,7 @@ export class ConversationService {
422
424
  }
423
425
  const outcome = await this.runtime.revert({
424
426
  conversation,
425
- cwd: workspace.path,
427
+ cwd: await resolveSessionCwd(workspace.path, conversation.cwd),
426
428
  boundaryEntryId: plan.boundaryEntryId,
427
429
  beforeBoundary: withdraw && Boolean(conversation.messages.find(item => item.id === messageId)?.piEntryId),
428
430
  });
@@ -433,6 +435,12 @@ export class ConversationService {
433
435
  const withdrawn = conversation.messages.find(item => item.id === messageId);
434
436
  const withdrawnContent = withdrawn ? { content: withdrawn.content, images: withdrawn.images } : undefined;
435
437
  const applied = await this.store.revertMessages(conversationId, messageId, withdraw);
438
+ // THE RECALL INDEX MUST SEE THE TRUNCATED TRANSCRIPT. `conversation` above is the DETACHED COPY
439
+ // that `turnContext` took BEFORE the store spliced its live array, so it still holds the withdrawn
440
+ // turns - re-syncing from it would put the text the person just withdrew straight back into the
441
+ // index, which is the exact leak this path exists to close. The store's post-revert conversation is
442
+ // the truth, and this rare destructive path is where one clone of it is the right price.
443
+ this.memory?.syncConversation(this.store.conversation(conversationId) ?? conversation);
436
444
  // THE MEMORY HALF OF THE REWIND, and the half that used to be missing. Everything above removes the
437
445
  // turn from Pi's file and from state.json; the memory store is a second durable copy of what the turn
438
446
  // said, and it is spliced into every later prompt. So the model kept being told about a turn the person
@@ -480,7 +488,7 @@ export class ConversationService {
480
488
  }
481
489
  try {
482
490
  const forkedId = newConversationId();
483
- const outcome = await this.runtime.fork({ conversation, cwd: workspace.path, forkedConversationId: forkedId });
491
+ const outcome = await this.runtime.fork({ conversation, cwd: await resolveSessionCwd(workspace.path, conversation.cwd), forkedConversationId: forkedId });
484
492
  const forked = await this.store.forkConversation(conversationId, forkedId);
485
493
  console.log(`Super Pi: forked conversation ${conversation.id} into ${forked.id}: ` +
486
494
  `${forked.messages.length} message(s) copied in state.json` +
@@ -161,6 +161,15 @@ export declare class RunService {
161
161
  resultPath: string;
162
162
  error?: string;
163
163
  }): Promise<void>;
164
+ /**
165
+ * Dispatches a background result (exec or child session) into the conversation.
166
+ *
167
+ * IDLE: starts a fresh turn via `processWaitingMessage`.
168
+ * BUSY: attempts to steer the message into the running turn so it is delivered at the
169
+ * very next tool batch boundary rather than waiting for the entire turn to complete.
170
+ * INTERRUPTING: quietly leaves the message waiting; the interrupted or next turn will pick it up.
171
+ */
172
+ private dispatchBackgroundResult;
164
173
  /**
165
174
  * Stops an active background task (spawned process or detached subagent) on user request.
166
175
  *
@@ -171,6 +180,8 @@ export declare class RunService {
171
180
  * updates immediately.
172
181
  */
173
182
  stopBackgroundTask(taskId: string): Promise<void>;
183
+ hasActiveBackgroundTasks(conversationId: string): boolean;
184
+ settleBackgroundStatus(conversationId: string): Promise<void>;
174
185
  /** Per-conversation command lane. All queue mutations and run claims use the same lane, so
175
186
  * two browser tabs cannot both observe an idle conversation and start duplicate turns. */
176
187
  private readonly conversationOps;
@@ -236,10 +247,10 @@ export declare class RunService {
236
247
  /**
237
248
  * Resolves conversation and workspace working directory for subagent dispatch.
238
249
  */
239
- dispatchContext(conversationId: string): {
250
+ dispatchContext(conversationId: string): Promise<{
240
251
  conversation: Conversation;
241
252
  cwd: string;
242
- };
253
+ }>;
243
254
  /**
244
255
  * Checks whether a conversation has an active turn running.
245
256
  */
@@ -379,11 +390,30 @@ export declare class RunService {
379
390
  * stream it - which is why the two cases are expressed identically and cannot drift apart.
380
391
  */
381
392
  runConversationTurns(conversation: Conversation, workspace: Workspace, prompt: string, controller: AbortController, firstMessageId: string, initialImages?: MessageImage[]): Promise<void>;
393
+ /**
394
+ * Refuses a turn when the selected model cannot accept image input, either because the incoming
395
+ * message carries images or because previous messages in the conversation contain images.
396
+ */
397
+ private assertModelSupportsImages;
382
398
  /** Keep admission owned through final persistence and recheck the durable queue before releasing. */
383
399
  private runUntilDrained;
384
400
  /**
385
401
  * Enqueues or starts execution for a user message.
386
402
  */
403
+ /**
404
+ * STARTS A TURN FROM A SENDER THAT IS NOT THE READER.
405
+ *
406
+ * `enqueueAndRun` below is the person's path: it writes the words under the reader's own name, in
407
+ * the reader's bubble, so the reader can see that they said it. The orchestrator's path is the
408
+ * same machinery with a different speaker - a primary agent handing a task to a child session -
409
+ * and writing THAT under the reader's name was the bug the reader reported: the child's
410
+ * transcript showed an instruction they never typed, wearing their avatar.
411
+ *
412
+ * So the write goes through `addAgentMessage` instead, which records the words as the system's,
413
+ * and the turn runs on them exactly as before. `composePrompt` is what tells the model who is
414
+ * speaking, so the model is addressed in the same words.
415
+ */
416
+ enqueueAndRunAsAgent(conversationId: string, content: string): Promise<void>;
387
417
  enqueueAndRun(conversationId: string, content: string, images?: MessageImage[], attachments?: import("../types.js").MessageAttachments, clientId?: string): Promise<void>;
388
418
  /**
389
419
  * Steers a queued message into the in-flight turn.