@evandu/super-pi 0.1.164 → 0.1.166

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 (48) hide show
  1. package/agents/coder.md +13 -1
  2. package/agents/critic.md +6 -2
  3. package/agents/patcher.md +6 -2
  4. package/agents/planner.md +14 -2
  5. package/agents/project-leader.md +25 -0
  6. package/agents/reviewer.md +13 -1
  7. package/agents/scout.md +6 -2
  8. package/agents/super-pi-task/core.ts +11 -7
  9. package/agents/verifier.md +6 -2
  10. package/dist/client/.super-pi-client-sources.json +10 -8
  11. package/dist/client/assets/{CodeMirrorEditor-B7hhyG33.js → CodeMirrorEditor-q5ckKYpW.js} +2 -2
  12. package/dist/client/assets/{dist-CdpuBRoy.js → dist-3Qp_-Z3t.js} +1 -1
  13. package/dist/client/assets/{dist-B5xG-jTK.js → dist-B61EUjUz.js} +1 -1
  14. package/dist/client/assets/{dist-BLhPsVFt.js → dist-BN9HZG4O.js} +1 -1
  15. package/dist/client/assets/{dist-CxZb6ATc.js → dist-BOU0i9JW.js} +1 -1
  16. package/dist/client/assets/{dist-BBfDBMAk.js → dist-BT3fZ0wT.js} +1 -1
  17. package/dist/client/assets/{dist-BDy5Nw4_.js → dist-B_l0KBjV.js} +1 -1
  18. package/dist/client/assets/{dist-Foaz_oK2.js → dist-Bb-gT7aj.js} +1 -1
  19. package/dist/client/assets/{dist-D607J8oC.js → dist-CBqBGuBl.js} +1 -1
  20. package/dist/client/assets/{dist-CGO5Bn1x.js → dist-CXXAxAE4.js} +1 -1
  21. package/dist/client/assets/{dist-DJ4sWDSy.js → dist-Crew6BC1.js} +1 -1
  22. package/dist/client/assets/{dist-BjlmPyRl.js → dist-Cs1P7JrO.js} +1 -1
  23. package/dist/client/assets/{dist-CsBiEr54.js → dist-CyTiEqGG.js} +1 -1
  24. package/dist/client/assets/{dist-7Nz91m3a.js → dist-D10kcMrw.js} +1 -1
  25. package/dist/client/assets/{dist-D6nsZZd3.js → dist-DMynScoj.js} +1 -1
  26. package/dist/client/assets/{dist-CrJV_eES.js → dist-DRHWMjx_.js} +1 -1
  27. package/dist/client/assets/{dist-qqDS87aN.js → dist-DSIy9Ers.js} +1 -1
  28. package/dist/client/assets/{dist-BmeBkDNB.js → dist-DdfZJR8e.js} +1 -1
  29. package/dist/client/assets/{dist-CF9zuubZ.js → dist-DiqGp2ie.js} +1 -1
  30. package/dist/client/assets/{dist-ByEzPyEi.js → dist-YFQ3ixZU.js} +1 -1
  31. package/dist/client/assets/{dist-DaUyp_NN.js → dist-_pAt4bRw.js} +1 -1
  32. package/dist/client/assets/{dist-BhouAfNI.js → dist-bLHDT2q5.js} +1 -1
  33. package/dist/client/assets/{dist-C4ql8ak8.js → dist-dbLsO_41.js} +1 -1
  34. package/dist/client/assets/{index-Wtlj0s4R.css → index-C6YWfUu2.css} +1 -1
  35. package/dist/client/assets/{index-1hS3wVqF.js → index-dARLbQ-8.js} +19 -19
  36. package/dist/client/index.html +2 -2
  37. package/dist/server/agents.d.ts +11 -0
  38. package/dist/server/agents.js +50 -6
  39. package/dist/server/application/project-service.d.ts +95 -4
  40. package/dist/server/application/project-service.js +328 -72
  41. package/dist/server/http-server.js +7 -0
  42. package/dist/server/store.d.ts +17 -0
  43. package/dist/server/store.js +97 -1
  44. package/dist/server/subagent-runs.js +19 -1
  45. package/dist/server/task-tool.d.ts +1 -1
  46. package/dist/server/task-tool.js +4 -3
  47. package/dist/server/types.d.ts +44 -1
  48. package/package.json +11 -2
@@ -924,6 +924,23 @@ export declare class Store {
924
924
  * as durable as the conversation it belongs to.
925
925
  */
926
926
  setSubagentRun(runId: string, changes: SubagentRunUpdate): Promise<SubagentRun>;
927
+ /**
928
+ * Adds ONE settled run's usage to the ledger of the conversation that dispatched it.
929
+ *
930
+ * CALLED AT THE SETTLE, ONCE PER RUN, and the run's FINAL accumulated usage is what it reads - the
931
+ * run record already holds the total of every finished message the child produced, so nothing is
932
+ * re-derived here and no message is counted twice.
933
+ *
934
+ * THE FLAG AND THE LEDGER ENTRY ARE WRITTEN IN ONE WRITE, and that is the whole crash-safety
935
+ * argument: `foldRunUsage` mutates both in the same synchronous block and this persists the state
936
+ * once, so a server that dies here cannot leave the money counted twice (flag written, entry not)
937
+ * or not at all (entry written, flag not). A run that was folded already is left alone, so calling
938
+ * this twice is a no-op rather than a second bill.
939
+ *
940
+ * IT PERSISTS THE WHOLE STATE rather than the two rows, because the two rows ARE one fact: the
941
+ * run's flag and the conversation's ledger entry have to become true together or not at all.
942
+ */
943
+ accountSubagentUsage(runId: string): Promise<boolean>;
927
944
  /**
928
945
  * Replaces the model's task list, and persists it.
929
946
  *
@@ -105,6 +105,71 @@ function reconcileSubagentRuns(state) {
105
105
  }
106
106
  return orphaned;
107
107
  }
108
+ /**
109
+ * ONE RUN'S USAGE, FOLDED INTO THE LEDGER OF THE CONVERSATION THAT DISPATCHED IT.
110
+ *
111
+ * WHY THE CHILD'S MONEY BELONGS TO THE PARENT'S LEDGER. A one-shot call spends real money on a
112
+ * child Pi, and the only number a budget is compared against is a conversation's `usage`. A ledger
113
+ * that held the parent's own turns alone would let a conversation multiply its real cost past its
114
+ * own cap simply by dispatching children instead of answering - which is precisely what design §5
115
+ * exists to prevent ("写预算的人必须能自己算出这个数是多少钱"). So the child's FINAL accumulated
116
+ * usage is added to the parent's, once, when the run settles.
117
+ *
118
+ * ONCE, AND ONLY ONCE, AND WHY THE FLAG IS THE WHOLE MECHANISM. A run reports its usage on every
119
+ * finished assistant message, and the recorder settles the run with the run's TOTAL - so folding
120
+ * per message would bill every message twice. The fold therefore happens once, on the settled run,
121
+ * and `usageAccounted` says which side of that line the run is on. The flag and the ledger entry
122
+ * are written in ONE store write, so a server that dies between them cannot leave the money
123
+ * counted twice or not at all.
124
+ *
125
+ * THE FIELDS ARE ADDED, NOT RE-DERIVED. A conversation's usage and a run's usage are the same
126
+ * shape (`input` / `output` / `cacheRead` / `cacheWrite` / `cost` / `turns`), so the sum is a sum -
127
+ * no second pricing formula, and no place where the two could disagree about what a token costs.
128
+ *
129
+ * A RUN WITH NO CONVERSATION IS NOT FOLDED, and is not an error: the conversation it belonged to
130
+ * was deleted while the child was running, so there is no ledger left to add to. Deleting a
131
+ * conversation deletes its runs too, so the money is not counted twice either - it is simply not
132
+ * counted, because nobody is left to be billed for it.
133
+ */
134
+ function foldRunUsage(state, run) {
135
+ if (!run?.usage || run.usageAccounted)
136
+ return false;
137
+ const conversation = (state.conversations ?? []).find((item) => item?.id === run.conversationId);
138
+ if (!conversation)
139
+ return false;
140
+ const previous = conversation.usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
141
+ conversation.usage = {
142
+ input: previous.input + run.usage.input,
143
+ output: previous.output + run.usage.output,
144
+ cacheRead: previous.cacheRead + run.usage.cacheRead,
145
+ cacheWrite: previous.cacheWrite + run.usage.cacheWrite,
146
+ cost: previous.cost + run.usage.cost,
147
+ turns: previous.turns + run.usage.turns,
148
+ };
149
+ conversation.usageUpdatedAt = now();
150
+ run.usageAccounted = true;
151
+ return true;
152
+ }
153
+ /**
154
+ * THE STARTUP SWEEP FOR RUNS THAT PREDATE THE LEDGER.
155
+ *
156
+ * A `state.json` written before the conversation ledger existed holds runs whose usage was never
157
+ * added to the conversation that dispatched them - because at the time, nothing did that. Left
158
+ * alone, the project's budget would silently DROP for every project that had already spent money
159
+ * on one-shot calls, which is the kind of change nobody can explain afterwards.
160
+ *
161
+ * So every run that carries usage and has not been accounted for is folded in here, once, on the
162
+ * next boot. The flag makes the sweep idempotent: a run folded by this sweep is flagged, and the
163
+ * next boot leaves it alone. Mutated in place, like every other sweep in this file, and persisted
164
+ * by the single write `open` performs when this reports a change.
165
+ */
166
+ function foldUnaccountedRunUsage(state) {
167
+ let folded = 0;
168
+ for (const run of state.subagentRuns ?? [])
169
+ if (foldRunUsage(state, run))
170
+ folded += 1;
171
+ return folded;
172
+ }
108
173
  /**
109
174
  * Why a conversation the previous process left `running` is failed, in the same spirit and for the same
110
175
  * audience as `ORPHANED_RUN_ERROR`: this lands in a field the chat pane renders verbatim, so it names the
@@ -634,11 +699,15 @@ export class Store {
634
699
  const interrupted = reconcileConversations(state);
635
700
  const requeued = reconcileWaitingMessages(state);
636
701
  const todos = reconcileTodos(state);
702
+ // THE USAGE SWEEP. A run recorded before the conversation ledger existed has its money sitting in
703
+ // the run record and nowhere else, so the ledger is brought up to date here - once, and only
704
+ // for the runs that carry usage and have not been accounted for.
705
+ const foldedUsage = foldUnaccountedRunUsage(state);
637
706
  const store = new Store(repository, normalizeMessageTimestamps(state));
638
707
  // Persisted here, in `open`, rather than left for the first unrelated write: a reconciled record that
639
708
  // is only rewritten by a later mutation would still read `running` to anything that opened the file
640
709
  // in the meantime, and would be reconciled a SECOND time on the next restart.
641
- if (defaulted || orphaned.length || orphanedWorkflows.length || orphanedTasks.changed || interrupted.length || requeued.recovered.length || requeued.changed || todos.changed) {
710
+ if (defaulted || orphaned.length || orphanedWorkflows.length || orphanedTasks.changed || interrupted.length || requeued.recovered.length || requeued.changed || todos.changed || foldedUsage) {
642
711
  for (const run of orphaned)
643
712
  console.warn(`Super Pi: sub-agent run ${run.id} was still marked running when the last server stopped - it is now failed, because no child process survived it.`);
644
713
  for (const plan of orphanedWorkflows)
@@ -660,6 +729,10 @@ export class Store {
660
729
  console.warn(`Super Pi: message ${entry.message.id} of conversation ${entry.conversationId} was still marked sending when the last server stopped - it is waiting again and cancellable, because the Pi that would have delivered it is gone.`);
661
730
  for (const entry of requeued.folded)
662
731
  console.warn(`Super Pi: message ${entry.message.id} of conversation ${entry.conversationId} was waiting in the old queue format - it has been moved into that conversation's transcript.`);
732
+ // Announced, because a budget that silently changed shape is a budget nobody can explain: this
733
+ // says the money those runs spent is now part of the conversations that dispatched them.
734
+ if (foldedUsage)
735
+ console.warn(`Super Pi: the usage of ${foldedUsage} sub-agent run(s) recorded before the usage ledger existed was added to the conversations that dispatched them.`);
663
736
  await store.persist();
664
737
  }
665
738
  return store;
@@ -2542,6 +2615,29 @@ export class Store {
2542
2615
  await this.persistTransaction({ kind: "subagent.replace", run: structuredClone(run) });
2543
2616
  return structuredClone(run);
2544
2617
  }
2618
+ /**
2619
+ * Adds ONE settled run's usage to the ledger of the conversation that dispatched it.
2620
+ *
2621
+ * CALLED AT THE SETTLE, ONCE PER RUN, and the run's FINAL accumulated usage is what it reads - the
2622
+ * run record already holds the total of every finished message the child produced, so nothing is
2623
+ * re-derived here and no message is counted twice.
2624
+ *
2625
+ * THE FLAG AND THE LEDGER ENTRY ARE WRITTEN IN ONE WRITE, and that is the whole crash-safety
2626
+ * argument: `foldRunUsage` mutates both in the same synchronous block and this persists the state
2627
+ * once, so a server that dies here cannot leave the money counted twice (flag written, entry not)
2628
+ * or not at all (entry written, flag not). A run that was folded already is left alone, so calling
2629
+ * this twice is a no-op rather than a second bill.
2630
+ *
2631
+ * IT PERSISTS THE WHOLE STATE rather than the two rows, because the two rows ARE one fact: the
2632
+ * run's flag and the conversation's ledger entry have to become true together or not at all.
2633
+ */
2634
+ async accountSubagentUsage(runId) {
2635
+ const run = this.state.subagentRuns.find((item) => item.id === runId);
2636
+ if (!foldRunUsage(this.state, run))
2637
+ return false;
2638
+ await this.persist();
2639
+ return true;
2640
+ }
2545
2641
  /**
2546
2642
  * Replaces the model's task list, and persists it.
2547
2643
  *
@@ -305,7 +305,7 @@ export class SubagentRunRecorder {
305
305
  // `output`; the streamed copy is only a fallback for a child that never sent a `done`.
306
306
  const output = result.finalText || streamed;
307
307
  const truncated = output.length > PER_TASK_OUTPUT_CAP;
308
- return await settle({
308
+ const settled = await settle({
309
309
  status: "completed",
310
310
  output: truncated ? output.slice(0, PER_TASK_OUTPUT_CAP) : output,
311
311
  // `undefined` rather than `false`: the key is absent on an untruncated run, so it serializes
@@ -321,6 +321,24 @@ export class SubagentRunRecorder {
321
321
  failure: undefined,
322
322
  finishedAt: new Date().toISOString(),
323
323
  });
324
+ // THE CHILD'S MONEY IS THE CALLER'S MONEY, AND IT IS ADDED ONCE.
325
+ //
326
+ // The run has just been settled with its FINAL accumulated usage, and that is the number the
327
+ // dispatching conversation's ledger is given - not one message's worth, and not one addition
328
+ // per message the child produced. A project budget reads the conversation's ledger, so a child
329
+ // that is not in there is a child nobody is charged for.
330
+ //
331
+ // A FAILURE HERE IS ANNOUNCED AND SWALLOWED: the run record is already on disk, so the work is
332
+ // real and recorded, and losing the ledger entry must not turn a finished run into a failed one.
333
+ // The next start folds it in - the startup sweep accounts for every run whose usage has not been
334
+ // accounted for - so the money is late rather than lost.
335
+ try {
336
+ await this.hooks.store.accountSubagentUsage(id);
337
+ }
338
+ catch (error) {
339
+ console.warn(`Super Pi: the usage of sub-agent run ${id} could not be added to the usage of conversation ${conversationId}: ${error instanceof Error ? error.message : "unknown error"}`);
340
+ }
341
+ return settled;
324
342
  }
325
343
  catch (error) {
326
344
  const stopped = error instanceof SubagentCancelled;
@@ -262,7 +262,7 @@ export declare const SESSION_STOP_TOOL_NAME = "session_stop";
262
262
  export declare const SESSION_REPORT_TOOL_NAME = "session_report";
263
263
  export declare const SESSION_STOP_SUBTREE_TOOL_NAME = "session_stop_subtree";
264
264
  /** Model-facing description for session_spawn. */
265
- export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.\n5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.\n6. Optionally pass 'model' to choose which model the NEW session runs on: {\"provider\":\"...\",\"modelId\":\"...\"} - the same \"provider/modelId\" pair the model picker shows, and BOTH halves are required. Leave 'model' out and the new session starts on the workspace default model.\n7. Optionally pass 'watchdogTimeoutSeconds' to declare how long YOU are willing to wait for a report before you want to be reminded: a positive number of seconds. If the new session stays silent that long, you receive a '[Watchdog \u63D0\u9192]' message naming it. A report from the child (session_report) restarts the clock; the watchdog never stops the child. Leave it out to run without a watchdog.";
265
+ export declare const SESSION_SPAWN_DESCRIPTION = "Create a child conversation in the current workspace and immediately dispatch its initial task. You can specify a primary agent as the child's role.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent child CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Multi-phase or complex work where the user needs each step to be visible and available for inspection or intervention: create a child session with session_spawn.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a single one-off delegation with a single answer: use 'task'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.\n5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.\n6. Optionally pass 'model' to choose which model the NEW session runs on: {\"provider\":\"...\",\"modelId\":\"...\"} - the same \"provider/modelId\" pair the model picker shows, and BOTH halves are required. Leave 'model' out and the new session starts on the workspace default model.\n7. Optionally pass 'watchdogTimeoutSeconds' to declare how long YOU are willing to wait for a report before you want to be reminded: a positive number of seconds. If the new session stays silent that long, you receive a '[Watchdog \u63D0\u9192]' message naming it. A report from the child (session_report) restarts the clock; the watchdog never stops the child. Leave it out to run without a watchdog.";
266
266
  /** Model-facing description for session_status. */
267
267
  export declare const SESSION_STATUS_DESCRIPTION = "Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.\n\nWHEN TO USE THIS:\n- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.\n- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.\n- To list all sibling conversations under your coordination when conversationId is omitted.\n\nUsage notes:\n1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).\n2. When 'conversationId' is omitted: returns a summary list of sibling conversations whose direct parent is 'parentConversationId' (the calling session by default).\n3. By default it never returns full transcripts; concise summaries keep your context window lean. Set 'includeOutput' with a conversationId to read the final output from its result file, capped to the last 64Ki characters with an explicit truncation notice.";
268
268
  /** Model-facing description for session_dispatch. */
@@ -367,21 +367,22 @@ export const SESSION_STOP_TOOL_NAME = "session_stop";
367
367
  export const SESSION_REPORT_TOOL_NAME = "session_report";
368
368
  export const SESSION_STOP_SUBTREE_TOOL_NAME = "session_stop_subtree";
369
369
  /** Model-facing description for session_spawn. */
370
- export const SESSION_SPAWN_DESCRIPTION = `Create a new sibling conversation in the current workspace and immediately dispatch its initial task.
370
+ export const SESSION_SPAWN_DESCRIPTION = `Create a child conversation in the current workspace and immediately dispatch its initial task. You can specify a primary agent as the child's role.
371
371
 
372
372
  DIFFERENCE FROM THE 'task' TOOL:
373
373
  - The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.
374
- - 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.
374
+ - 'session_spawn' creates a real, persistent child CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.
375
375
 
376
376
  WHEN TO USE THIS:
377
377
  - Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.
378
+ - Multi-phase or complex work where the user needs each step to be visible and available for inspection or intervention: create a child session with session_spawn.
378
379
  - Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.
379
380
  - When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.
380
381
 
381
382
  WHEN NOT TO USE THIS:
382
383
  - For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.
383
384
  - For running shell commands or build watchers: use 'run_async'.
384
- - For a linear multi-step subagent pipeline: use 'workflow'.
385
+ - For a single one-off delegation with a single answer: use 'task'.
385
386
 
386
387
  Usage notes:
387
388
  1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.
@@ -464,7 +464,17 @@ export type Conversation = {
464
464
  status: RunStatus;
465
465
  messages: Message[];
466
466
  archivedAt?: string;
467
- /** Cumulative primary-assistant usage observed since readout collection began; excludes subagents. */
467
+ /**
468
+ * Cumulative usage observed for this conversation: its OWN assistant messages AND the one-shot
469
+ * (sub-agent) calls it dispatched.
470
+ *
471
+ * THE SUB-AGENT'S USAGE IS IN HERE ON PURPOSE. A child Pi spends real money, and a budget that
472
+ * only counts the parent's own turns is a budget that can be multiplied past its own cap by
473
+ * dispatching children instead of answering - which is exactly what design §5 forbids, because
474
+ * "写预算的人必须能自己算出这个数是多少钱" is only true if the number includes every call the
475
+ * conversation made. The child's final accumulated usage is folded in ONCE, when the run settles
476
+ * (see `Store.accountSubagentUsage`), so the sum here equals what was really spent.
477
+ */
468
478
  usage?: SubagentUsage;
469
479
  /** False means cost is only a sum of reported costs, not a complete price (zero is not "free"). */
470
480
  usageCostKnown?: boolean;
@@ -601,6 +611,21 @@ export type AgentDefinition = {
601
611
  prompt: string;
602
612
  /** The directory this agent definition was discovered from. */
603
613
  sourceDir?: string;
614
+ /**
615
+ * Whether this definition came from the PACKAGE'S OWN agents directory - the ones Super Pi ships -
616
+ * rather than from a directory the user pointed the app at.
617
+ *
618
+ * WHY IT IS DECIDED BY THE SERVER AND NOT BY THE CLIENT. The picker used to interleave the two
619
+ * kinds alphabetically, so the built-in agents - the ones with a colour dot - were scattered among
620
+ * the user's own. The client cannot work that out on its own: it is told where each definition came
621
+ * from, but "is this directory the package's own" is a question about the app's installation, and
622
+ * only the server knows where it is installed. So the server answers it here, at read time, and the
623
+ * client just orders by the answer.
624
+ *
625
+ * It describes the definition that WON, not the id: when a user's directory shadows a built-in
626
+ * agent of the same id, the user's file is the one that is listed, and it is the user's.
627
+ */
628
+ builtin?: boolean;
604
629
  };
605
630
  /**
606
631
  * What the picker receives. The prompt body is deliberately NOT sent: it is a whole system prompt,
@@ -830,6 +855,20 @@ export type SubagentRun = {
830
855
  /** True when `output` was cut for the model's benefit; the browser still saw all of it. */
831
856
  outputTruncated?: boolean;
832
857
  usage?: SubagentUsage;
858
+ /**
859
+ * TRUE once this run's usage has been folded into the usage ledger of the conversation that
860
+ * dispatched it.
861
+ *
862
+ * IT IS THE FLAG THAT MAKES THE FOLD HAPPEN EXACTLY ONCE. The child's money is added to the
863
+ * parent conversation's `usage` when the run settles, and a project budget reads that ledger -
864
+ * so a run whose usage were folded twice would be billed twice, and a run whose usage were never
865
+ * folded would be free. The flag says which side of that line this run is on, and it is written
866
+ * in the SAME store write as the ledger entry, so there is no window in which the two disagree.
867
+ *
868
+ * ABSENT means "not accounted for yet", which is what a `state.json` written before this feature
869
+ * existed says about every run in it - and what the startup sweep repairs, once, on the next boot.
870
+ */
871
+ usageAccounted?: boolean;
833
872
  error?: string;
834
873
  /**
835
874
  * How long the run took, measured by THIS server between writing the `running` row and settling it.
@@ -949,6 +988,7 @@ export type ProjectTask = {
949
988
  priority: "low" | "medium" | "high" | "urgent";
950
989
  dueAt?: string;
951
990
  blockedReason?: string;
991
+ blockedReportedByConversationId?: string;
952
992
  output?: string;
953
993
  evidenceRefs?: string[];
954
994
  /** The acceptance of this task. `lightweight: true` means the executor accepted its own
@@ -989,6 +1029,9 @@ export type ProjectEscalation = {
989
1029
  decision?: string;
990
1030
  createdAt: string;
991
1031
  resolvedAt?: string;
1032
+ audience?: "supervisor" | "boss";
1033
+ targetConversationId?: string;
1034
+ resolvedByConversationId?: string;
992
1035
  };
993
1036
  export type ProjectEvent = {
994
1037
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.164",
3
+ "version": "0.1.166",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,6 +48,7 @@
48
48
  "scripts/stream-transport.test.mjs",
49
49
  "scripts/remote-transport.test.mjs",
50
50
  "scripts/browser-safe-port.test.mjs",
51
+ "scripts/agent-picker-order.test.mjs",
51
52
  "scripts/inline-thinking.test.mjs",
52
53
  "scripts/inline-thinking-close-tag.test.mjs",
53
54
  "scripts/pi-session.test.mjs",
@@ -69,6 +70,7 @@
69
70
  "scripts/verify-source-text.test.mjs",
70
71
  "scripts/runtime-extension-ui.test.mjs",
71
72
  "scripts/agent-mechanism.test.mjs",
73
+ "scripts/builtin-agents-project.test.mjs",
72
74
  "scripts/super-pi-tool-names.test.mjs",
73
75
  "scripts/agent-wiring.test.mjs",
74
76
  "scripts/message-timestamps.test.mjs",
@@ -85,6 +87,7 @@
85
87
  "scripts/session-orchestrator-tools.test.mjs",
86
88
  "scripts/project-service.test.mjs",
87
89
  "scripts/project-api-scope.test.mjs",
90
+ "scripts/project-report-chain.test.mjs",
88
91
  "scripts/session-tree-collapse.test.mjs",
89
92
  "scripts/archive-cascade.test.mjs",
90
93
  "scripts/workspace-cwd.test.mjs",
@@ -94,6 +97,7 @@
94
97
  "scripts/subagent-noise.test.mjs",
95
98
  "scripts/subagent-response-receipt.test.mjs",
96
99
  "scripts/subagent-runs.test.mjs",
100
+ "scripts/subagent-usage-accounting.test.mjs",
97
101
  "scripts/ws-flood-regression.test.mjs",
98
102
  "scripts/conversation-reconcile.test.mjs",
99
103
  "scripts/subagent-dispatch.test.mjs",
@@ -128,6 +132,7 @@
128
132
  "scripts/git-write-endpoint-guard.test.mjs",
129
133
  "scripts/question-stop.test.mjs",
130
134
  "scripts/background-result-body.test.mjs",
135
+ "scripts/browser-click.test.mjs",
131
136
  "scripts/question-request-size.test.mjs",
132
137
  "scripts/md-render.test.mjs",
133
138
  "scripts/revert-session-format.test.mjs",
@@ -224,7 +229,9 @@
224
229
  "scripts/session-report-tool.test.mjs",
225
230
  "scripts/session-stop-subtree.test.mjs",
226
231
  "scripts/awaiting-background-status.test.mjs",
227
- "scripts/project-lifecycle-e2e.test.mjs"
232
+ "scripts/project-lifecycle-e2e.test.mjs",
233
+ "scripts/agent-tiers.test.mjs",
234
+ "scripts/design-principles.test.mjs"
228
235
  ],
229
236
  "check:browser": [
230
237
  "scripts/interrupt-browser.test.mjs",
@@ -258,6 +265,8 @@
258
265
  "scripts/workspace-identity-browser.test.mjs",
259
266
  "scripts/sidebar-tools-browser.test.mjs",
260
267
  "scripts/project-employee-panel-browser.test.mjs",
268
+ "scripts/project-evidence-panel-browser.test.mjs",
269
+ "scripts/builtin-agents-project-browser.test.mjs",
261
270
  "scripts/tool-runtime-browser.test.mjs",
262
271
  "scripts/session-context-menu-browser.test.mjs",
263
272
  "scripts/archive-cascade-browser.test.mjs",