@evandu/super-pi 0.1.148 → 0.1.149

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.
@@ -35,5 +35,5 @@
35
35
  "src/client/types.ts": "7c06189b32890009d28a371a64d9f29161c14a349a5b399d51d1314d38fa27ac",
36
36
  "src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
37
37
  },
38
- "recordedAt": "2026-11-01T14:41:49.801Z"
38
+ "recordedAt": "2026-11-01T14:53:57.934Z"
39
39
  }
@@ -110,16 +110,27 @@ export declare class SessionOrchestratorService {
110
110
  */
111
111
  private readonly lengthContinuations;
112
112
  /**
113
- * THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT.
113
+ * THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT, AND WHEN.
114
114
  *
115
115
  * A child can report explicitly - the `report` action - and that report is the child's own
116
116
  * statement of what it did. When the child's turn then ends, the automatic completion report
117
117
  * would deliver the same thing a second time, so the automatic path steps aside for a child that
118
- * is in this set.
118
+ * is in this map.
119
119
  *
120
120
  * The entry is added only by a TERMINAL report (`completed` / `failed`), because an interim
121
121
  * update is not the child's final word: the task is still running, and the parent will want to
122
122
  * hear how it ended.
123
+ *
124
+ * WHY THE MAP HOLDS A TIMESTAMP AND NOT A FLAG. A flag says "this child reported", and that is
125
+ * not the same question as "this child reported ABOUT THE TURN THAT JUST ENDED". A flag stays
126
+ * true for the rest of the child's life, so it silences every automatic report the child ever
127
+ * produces again: the parent gives the same child a second task, the child finishes, and the
128
+ * result is swallowed because of a report the parent received about the FIRST task. The parent
129
+ * waits for a result that was written and never arrives, and nothing in the transcript says why.
130
+ *
131
+ * The timestamp answers the real question: the report was made INSIDE this turn, so it speaks for
132
+ * this turn. A report made before the turn started speaks for an earlier one, and this turn's
133
+ * result is still its own.
123
134
  */
124
135
  private readonly reportedChildren;
125
136
  constructor(store: Store, conversationService: ConversationService, runService: RunService, ledger: SessionLedger, broadcaster?: EventBroadcaster | undefined, watchdog?: SessionWatchdog);
@@ -39,18 +39,29 @@ export class SessionOrchestratorService {
39
39
  */
40
40
  lengthContinuations = new Map();
41
41
  /**
42
- * THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT.
42
+ * THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT, AND WHEN.
43
43
  *
44
44
  * A child can report explicitly - the `report` action - and that report is the child's own
45
45
  * statement of what it did. When the child's turn then ends, the automatic completion report
46
46
  * would deliver the same thing a second time, so the automatic path steps aside for a child that
47
- * is in this set.
47
+ * is in this map.
48
48
  *
49
49
  * The entry is added only by a TERMINAL report (`completed` / `failed`), because an interim
50
50
  * update is not the child's final word: the task is still running, and the parent will want to
51
51
  * hear how it ended.
52
+ *
53
+ * WHY THE MAP HOLDS A TIMESTAMP AND NOT A FLAG. A flag says "this child reported", and that is
54
+ * not the same question as "this child reported ABOUT THE TURN THAT JUST ENDED". A flag stays
55
+ * true for the rest of the child's life, so it silences every automatic report the child ever
56
+ * produces again: the parent gives the same child a second task, the child finishes, and the
57
+ * result is swallowed because of a report the parent received about the FIRST task. The parent
58
+ * waits for a result that was written and never arrives, and nothing in the transcript says why.
59
+ *
60
+ * The timestamp answers the real question: the report was made INSIDE this turn, so it speaks for
61
+ * this turn. A report made before the turn started speaks for an earlier one, and this turn's
62
+ * result is still its own.
52
63
  */
53
- reportedChildren = new Set();
64
+ reportedChildren = new Map();
54
65
  constructor(store, conversationService, runService, ledger, broadcaster, watchdog) {
55
66
  this.store = store;
56
67
  this.conversationService = conversationService;
@@ -180,6 +191,11 @@ export class SessionOrchestratorService {
180
191
  // writes the words under the reader's own name - so the child's transcript showed an
181
192
  // instruction the reader never typed, wearing the reader's avatar, and a reader who looked at
182
193
  // the child session saw their own name on somebody else's order.
194
+ //
195
+ // THE TURN'S START IS RECORDED HERE, because the question "did this child already report THIS
196
+ // turn?" is answered by comparing the report's time against it. A report made inside the turn
197
+ // speaks for the turn; a report made before it speaks for an earlier one.
198
+ const turnStartedAt = Date.now();
183
199
  const runPromise = this.runService.enqueueAndRunAsAgent(conversationId, content);
184
200
  const completion = (async () => {
185
201
  let failure;
@@ -238,9 +254,13 @@ export class SessionOrchestratorService {
238
254
  // is what it produced"; the explicit report says "the child is telling its parent something
239
255
  // now", and can be called at any point in the task's life.
240
256
  const turnEnded = current?.status === "completed" || current?.status === "failed";
241
- // A CHILD THAT REPORTED ITSELF IS NOT REPORTED TWICE. The explicit terminal report is the
242
- // child's own statement of what it did, so the automatic completion steps aside for it.
243
- const alreadyReported = this.reportedChildren.has(conversationId);
257
+ // A CHILD THAT REPORTED ITSELF IS NOT REPORTED TWICE - BUT ONLY FOR THE TURN IT REPORTED.
258
+ // The explicit terminal report is the child's own statement of what it did, so the
259
+ // automatic completion steps aside for it - when the report was made inside THIS turn.
260
+ // A report from an earlier turn says nothing about this one's result, and silencing this
261
+ // one because of that would swallow a result the parent is still waiting for.
262
+ const reportedAt = this.reportedChildren.get(conversationId);
263
+ const alreadyReported = reportedAt !== undefined && reportedAt >= turnStartedAt;
244
264
  if (turnEnded && !alreadyReported) {
245
265
  await this.runService.enqueueSessionResult({
246
266
  conversationId,
@@ -260,6 +280,16 @@ export class SessionOrchestratorService {
260
280
  // turn an already-finished child turn into a second failure.
261
281
  console.warn(`Super Pi: session result callback failed for ${conversationId}:`, error instanceof Error ? error.message : String(error));
262
282
  }
283
+ finally {
284
+ // THE RECORD IS SPENT BY THE TURN IT SPOKE FOR. Whatever it was about, that turn is over.
285
+ //
286
+ // A record left behind would be harmless - the timestamp comparison already keeps it from
287
+ // silencing a later turn - but it would keep saying "this child reported" to every future
288
+ // reader of this code, and the next thing that consults it would have to re-derive the
289
+ // timestamp rule. Dropping it here keeps one rule in one place: a report belongs to the
290
+ // turn that was running when it was made.
291
+ this.reportedChildren.delete(conversationId);
292
+ }
263
293
  if (failure !== undefined) {
264
294
  console.warn(`Super Pi: dispatch failed for conversation ${conversationId}:`, failure instanceof Error ? failure.message : String(failure));
265
295
  this.ledger.update(conversationId, { status: "failed" });
@@ -391,7 +421,10 @@ export class SessionOrchestratorService {
391
421
  // does not silence the automatic report: the child is still working, and the parent will want
392
422
  // to hear how the task ended.
393
423
  if (status === "completed" || status === "failed") {
394
- this.reportedChildren.add(childConversationId);
424
+ // THE TIME OF THE REPORT IS THE PART THAT MATTERS. It is what tells the completion path
425
+ // whether this report spoke for the turn that is ending or for an earlier one, and it is
426
+ // stamped here - where the report is made - rather than where it is looked up later.
427
+ this.reportedChildren.set(childConversationId, Date.now());
395
428
  await this.watchdog.cancel(childConversationId);
396
429
  }
397
430
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.148",
3
+ "version": "0.1.149",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",