@astralyn/pi 1.4.9 → 1.4.10

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 (204) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/bundle/chunks/{chunk-3PKXUKTB.js → chunk-6GTUUUCJ.js} +123 -123
  3. package/dist/bundle/chunks/{chunk-BAGHIP5J.js → chunk-MF3T3ETJ.js} +3 -3
  4. package/dist/bundle/chunks/{chunk-F66AY3WR.js → chunk-UCH27AVQ.js} +1 -1
  5. package/dist/bundle/chunks/{execute-P22VMM2H.js → execute-FQKRO4XJ.js} +1 -1
  6. package/dist/bundle/chunks/{virtual-modules-VKMBQTR2.js → virtual-modules-6IJ6V2CG.js} +1 -1
  7. package/dist/bundle/cli.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/core/agent-session.d.ts +34 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +99 -2
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/isolated-session.d.ts +7 -2
  15. package/dist/core/isolated-session.d.ts.map +1 -1
  16. package/dist/core/isolated-session.js +7 -3
  17. package/dist/core/isolated-session.js.map +1 -1
  18. package/dist/core/session-manager.d.ts +17 -1
  19. package/dist/core/session-manager.d.ts.map +1 -1
  20. package/dist/core/session-manager.js +29 -0
  21. package/dist/core/session-manager.js.map +1 -1
  22. package/dist/core/tasks/delivery.d.ts +42 -7
  23. package/dist/core/tasks/delivery.d.ts.map +1 -1
  24. package/dist/core/tasks/delivery.js +129 -15
  25. package/dist/core/tasks/delivery.js.map +1 -1
  26. package/dist/core/tasks/execution.d.ts +27 -14
  27. package/dist/core/tasks/execution.d.ts.map +1 -1
  28. package/dist/core/tasks/execution.js +90 -86
  29. package/dist/core/tasks/execution.js.map +1 -1
  30. package/dist/core/tasks/limits.d.ts +13 -1
  31. package/dist/core/tasks/limits.d.ts.map +1 -1
  32. package/dist/core/tasks/limits.js +14 -1
  33. package/dist/core/tasks/limits.js.map +1 -1
  34. package/dist/core/tasks/output.d.ts +9 -1
  35. package/dist/core/tasks/output.d.ts.map +1 -1
  36. package/dist/core/tasks/output.js +23 -10
  37. package/dist/core/tasks/output.js.map +1 -1
  38. package/dist/core/tasks/presentation.d.ts +1 -1
  39. package/dist/core/tasks/presentation.d.ts.map +1 -1
  40. package/dist/core/tasks/presentation.js +6 -4
  41. package/dist/core/tasks/presentation.js.map +1 -1
  42. package/dist/core/tasks/record.d.ts +41 -29
  43. package/dist/core/tasks/record.d.ts.map +1 -1
  44. package/dist/core/tasks/record.js +82 -37
  45. package/dist/core/tasks/record.js.map +1 -1
  46. package/dist/core/tasks/reminder.js +2 -2
  47. package/dist/core/tasks/reminder.js.map +1 -1
  48. package/dist/core/tasks/runtime.d.ts +17 -14
  49. package/dist/core/tasks/runtime.d.ts.map +1 -1
  50. package/dist/core/tasks/runtime.js +68 -116
  51. package/dist/core/tasks/runtime.js.map +1 -1
  52. package/dist/core/tasks/scheduler.d.ts +3 -1
  53. package/dist/core/tasks/scheduler.d.ts.map +1 -1
  54. package/dist/core/tasks/scheduler.js +16 -12
  55. package/dist/core/tasks/scheduler.js.map +1 -1
  56. package/dist/core/tasks/session.d.ts +24 -3
  57. package/dist/core/tasks/session.d.ts.map +1 -1
  58. package/dist/core/tasks/session.js +73 -9
  59. package/dist/core/tasks/session.js.map +1 -1
  60. package/dist/core/tasks/store.d.ts +17 -4
  61. package/dist/core/tasks/store.d.ts.map +1 -1
  62. package/dist/core/tasks/store.js +45 -48
  63. package/dist/core/tasks/store.js.map +1 -1
  64. package/dist/core/tasks/types.d.ts +72 -29
  65. package/dist/core/tasks/types.d.ts.map +1 -1
  66. package/dist/core/tasks/types.js +7 -0
  67. package/dist/core/tasks/types.js.map +1 -1
  68. package/dist/core/tasks/wait.d.ts +19 -0
  69. package/dist/core/tasks/wait.d.ts.map +1 -0
  70. package/dist/core/tasks/wait.js +45 -0
  71. package/dist/core/tasks/wait.js.map +1 -0
  72. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  73. package/dist/core/tools/shell-execution.js +2 -1
  74. package/dist/core/tools/shell-execution.js.map +1 -1
  75. package/dist/core/tools/shell-tool.d.ts.map +1 -1
  76. package/dist/core/tools/shell-tool.js +20 -13
  77. package/dist/core/tools/shell-tool.js.map +1 -1
  78. package/dist/extensions/agent/activity-lines.d.ts +10 -0
  79. package/dist/extensions/agent/activity-lines.d.ts.map +1 -0
  80. package/dist/extensions/agent/activity-lines.js +76 -0
  81. package/dist/extensions/agent/activity-lines.js.map +1 -0
  82. package/dist/extensions/agent/activity.d.ts +25 -4
  83. package/dist/extensions/agent/activity.d.ts.map +1 -1
  84. package/dist/extensions/agent/activity.js +76 -21
  85. package/dist/extensions/agent/activity.js.map +1 -1
  86. package/dist/extensions/agent/artifacts.d.ts +15 -0
  87. package/dist/extensions/agent/artifacts.d.ts.map +1 -0
  88. package/dist/extensions/agent/artifacts.js +44 -0
  89. package/dist/extensions/agent/artifacts.js.map +1 -0
  90. package/dist/extensions/agent/progress.d.ts +30 -4
  91. package/dist/extensions/agent/progress.d.ts.map +1 -1
  92. package/dist/extensions/agent/progress.js +84 -11
  93. package/dist/extensions/agent/progress.js.map +1 -1
  94. package/dist/extensions/agent/render.d.ts.map +1 -1
  95. package/dist/extensions/agent/render.js +8 -24
  96. package/dist/extensions/agent/render.js.map +1 -1
  97. package/dist/extensions/agent/report.d.ts +11 -3
  98. package/dist/extensions/agent/report.d.ts.map +1 -1
  99. package/dist/extensions/agent/report.js +32 -7
  100. package/dist/extensions/agent/report.js.map +1 -1
  101. package/dist/extensions/agent/tool.d.ts.map +1 -1
  102. package/dist/extensions/agent/tool.js +20 -5
  103. package/dist/extensions/agent/tool.js.map +1 -1
  104. package/dist/extensions/agent/types.d.ts +2 -0
  105. package/dist/extensions/agent/types.d.ts.map +1 -1
  106. package/dist/extensions/agent/types.js.map +1 -1
  107. package/dist/extensions/agent/worker.d.ts +7 -0
  108. package/dist/extensions/agent/worker.d.ts.map +1 -1
  109. package/dist/extensions/agent/worker.js +9 -3
  110. package/dist/extensions/agent/worker.js.map +1 -1
  111. package/dist/extensions/search/index.d.ts.map +1 -1
  112. package/dist/extensions/search/index.js +6 -7
  113. package/dist/extensions/search/index.js.map +1 -1
  114. package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
  115. package/dist/extensions/tasks/tool/actions.js +15 -11
  116. package/dist/extensions/tasks/tool/actions.js.map +1 -1
  117. package/dist/extensions/tasks/tool/schema.d.ts +1 -1
  118. package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
  119. package/dist/extensions/tasks/tool/schema.js +5 -5
  120. package/dist/extensions/tasks/tool/schema.js.map +1 -1
  121. package/dist/extensions/tasks/tool/types.d.ts +2 -0
  122. package/dist/extensions/tasks/tool/types.d.ts.map +1 -1
  123. package/dist/extensions/tasks/tool/types.js.map +1 -1
  124. package/dist/extensions/tasks/tool.d.ts.map +1 -1
  125. package/dist/extensions/tasks/tool.js +3 -2
  126. package/dist/extensions/tasks/tool.js.map +1 -1
  127. package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
  128. package/dist/extensions/tasks/ui/completion-render.js +2 -2
  129. package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
  130. package/dist/extensions/tasks/ui/footer.d.ts +37 -0
  131. package/dist/extensions/tasks/ui/footer.d.ts.map +1 -0
  132. package/dist/extensions/tasks/ui/footer.js +129 -0
  133. package/dist/extensions/tasks/ui/footer.js.map +1 -0
  134. package/dist/extensions/tasks/ui/frame.d.ts +36 -0
  135. package/dist/extensions/tasks/ui/frame.d.ts.map +1 -0
  136. package/dist/extensions/tasks/ui/frame.js +33 -0
  137. package/dist/extensions/tasks/ui/frame.js.map +1 -0
  138. package/dist/extensions/tasks/ui/help.d.ts +6 -0
  139. package/dist/extensions/tasks/ui/help.d.ts.map +1 -0
  140. package/dist/extensions/tasks/ui/help.js +53 -0
  141. package/dist/extensions/tasks/ui/help.js.map +1 -0
  142. package/dist/extensions/tasks/ui/inspector.d.ts +4 -3
  143. package/dist/extensions/tasks/ui/inspector.d.ts.map +1 -1
  144. package/dist/extensions/tasks/ui/inspector.js +25 -6
  145. package/dist/extensions/tasks/ui/inspector.js.map +1 -1
  146. package/dist/extensions/tasks/ui/layout.d.ts +15 -18
  147. package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
  148. package/dist/extensions/tasks/ui/layout.js +162 -255
  149. package/dist/extensions/tasks/ui/layout.js.map +1 -1
  150. package/dist/extensions/tasks/ui/list.d.ts +3 -1
  151. package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
  152. package/dist/extensions/tasks/ui/list.js +19 -14
  153. package/dist/extensions/tasks/ui/list.js.map +1 -1
  154. package/dist/extensions/tasks/ui/manager.d.ts +17 -5
  155. package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
  156. package/dist/extensions/tasks/ui/manager.js +147 -90
  157. package/dist/extensions/tasks/ui/manager.js.map +1 -1
  158. package/dist/extensions/tasks/ui/model.d.ts +32 -5
  159. package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
  160. package/dist/extensions/tasks/ui/model.js +7 -7
  161. package/dist/extensions/tasks/ui/model.js.map +1 -1
  162. package/dist/extensions/tasks/ui/panes.d.ts +40 -0
  163. package/dist/extensions/tasks/ui/panes.d.ts.map +1 -0
  164. package/dist/extensions/tasks/ui/panes.js +144 -0
  165. package/dist/extensions/tasks/ui/panes.js.map +1 -0
  166. package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
  167. package/dist/extensions/tasks/ui/render.js +11 -7
  168. package/dist/extensions/tasks/ui/render.js.map +1 -1
  169. package/dist/extensions/tasks/ui/viewport.d.ts +12 -4
  170. package/dist/extensions/tasks/ui/viewport.d.ts.map +1 -1
  171. package/dist/extensions/tasks/ui/viewport.js +23 -7
  172. package/dist/extensions/tasks/ui/viewport.js.map +1 -1
  173. package/dist/extensions/tasks/ui/views.d.ts +12 -6
  174. package/dist/extensions/tasks/ui/views.d.ts.map +1 -1
  175. package/dist/extensions/tasks/ui/views.js +39 -32
  176. package/dist/extensions/tasks/ui/views.js.map +1 -1
  177. package/dist/modes/interactive/components/session-selector.d.ts +11 -1
  178. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/session-selector.js +43 -17
  180. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  181. package/dist/modes/print-mode.d.ts.map +1 -1
  182. package/dist/modes/print-mode.js +37 -2
  183. package/dist/modes/print-mode.js.map +1 -1
  184. package/dist/modes/rpc/rpc-client.d.ts +11 -0
  185. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  186. package/dist/modes/rpc/rpc-client.js +48 -0
  187. package/dist/modes/rpc/rpc-client.js.map +1 -1
  188. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  189. package/dist/modes/rpc/rpc-mode.js +1 -0
  190. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  191. package/dist/modes/rpc/rpc-types.d.ts +2 -0
  192. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  193. package/dist/modes/rpc/rpc-types.js.map +1 -1
  194. package/docs/bundled/extensions/agent.md +39 -7
  195. package/docs/bundled/tasks.md +25 -21
  196. package/docs/extensions.md +14 -10
  197. package/docs/json.md +3 -0
  198. package/docs/rpc.md +14 -0
  199. package/docs/sdk.md +11 -1
  200. package/docs/session-format.md +10 -4
  201. package/docs/sessions.md +1 -1
  202. package/docs/usage.md +2 -0
  203. package/npm-shrinkwrap.json +2 -2
  204. package/package.json +1 -1
@@ -1,15 +1,17 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import type { Usage } from "@earendil-works/pi-ai/compat";
3
3
  import type { CustomMessage } from "../messages.ts";
4
- import type { SessionEntry, SessionManager } from "../session-manager.ts";
4
+ import { type SessionEntry, type SessionManager } from "../session-manager.ts";
5
5
  import { TaskDelivery } from "./delivery.ts";
6
6
  import { TaskRuntime } from "./runtime.ts";
7
- import type { TaskSnapshot } from "./types.ts";
7
+ import { type TaskSnapshot } from "./types.ts";
8
8
  interface TaskSessionOptions {
9
9
  manager: SessionManager;
10
10
  backgroundAllowed?: boolean;
11
11
  /** The main session owns input queues, preflight and idle state. */
12
12
  canDeliver(): boolean;
13
+ /** Whether the model-facing management tool is active, which new background work requires. Omitted: no gate. */
14
+ managementActive?(): boolean;
13
15
  /**
14
16
  * Start a completion turn, or enqueue the message and return waitForPersistence().
15
17
  * The host owns input routing; this adapter owns the queued delivery's lifetime.
@@ -24,7 +26,11 @@ export interface QuarantinedTaskSettlement {
24
26
  task: TaskSnapshot;
25
27
  usage?: Usage;
26
28
  }
27
- /** Session persistence and completion delivery; execution supervision stays in TaskRuntime. */
29
+ /**
30
+ * Session persistence and completion delivery; execution supervision stays in TaskRuntime.
31
+ * A handed-off task that settles on the current service is persisted here and then enqueued in
32
+ * TaskDelivery, which keeps it retained until the model has seen it.
33
+ */
28
34
  export declare class TaskSession {
29
35
  private readonly options;
30
36
  private _service;
@@ -35,10 +41,18 @@ export declare class TaskSession {
35
41
  private readonly contextReminder;
36
42
  readonly delivery: TaskDelivery;
37
43
  private readonly quarantine;
44
+ private readonly changeListeners;
38
45
  constructor(options: TaskSessionOptions);
46
+ /** Called when background tasks, the runtime itself, or delivery state change. */
47
+ subscribe(listener: () => void): () => void;
48
+ private changed;
49
+ /** Handed-off tasks of the current runtime that have not settled. */
50
+ runningBackground(): TaskSnapshot[];
39
51
  get service(): TaskRuntime;
40
52
  /** End only current task waits; the next wait observes a fresh signal. */
41
53
  interruptWaits(): void;
54
+ /** The active tools changed: availability of background detaching may have too. */
55
+ toolsChanged(): void;
42
56
  noteCompaction(): void;
43
57
  projectContextReminder(messages: AgentMessage[]): AgentMessage[];
44
58
  noteAssistantCompleted(): void;
@@ -53,8 +67,15 @@ export declare class TaskSession {
53
67
  /** Cancel running tasks launched outside the branch ending at `leafId`. */
54
68
  cancelOutsideBranch(leafId: string | null): Promise<void>;
55
69
  private branchIds;
70
+ /**
71
+ * Close the current service, release the retention its undelivered completions hold, and
72
+ * drain its executors within the grace period. Lifecycle operations pair this with replaceService().
73
+ */
74
+ shutdown(graceMs?: number): Promise<void>;
56
75
  /** Called after the old service's bounded shutdown, while lifecycle delivery is paused. */
57
76
  replaceService(): void;
77
+ /** Acknowledge that a task's completion reached its consumer by other means than automatic delivery. */
78
+ acknowledgeDelivery(taskId: string): void;
58
79
  private createService;
59
80
  restoreHistory(service?: TaskRuntime): void;
60
81
  private quarantineSettlement;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,+BAA+B,CAAC;AACnF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI1E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,UAAU,kBAAkB;IAC3B,OAAO,EAAE,cAAc,CAAC;IACxB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oEAAoE;IACpE,UAAU,IAAI,OAAO,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxF,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,yBAAyB;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;CACd;AAED,+FAA+F;AAC/F,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;gBAElD,OAAO,EAAE,kBAAkB;IAWvC,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,0EAA0E;IAC1E,cAAc,IAAI,IAAI;IAMtB,cAAc,IAAI,IAAI;IAItB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE;IAIhE,sBAAsB,IAAI,IAAI;IAI9B,IAAI,sBAAsB,IAAI,SAAS,yBAAyB,EAAE,CAEjE;IAED,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKlC;;;;OAIG;IACH,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,cAAe,GAAG,YAAY,EAAE;IAUjF,2EAA2E;IAC3E,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,OAAO,CAAC,SAAS;IAIjB,2FAA2F;IAC3F,cAAc,IAAI,IAAI;IAOtB,OAAO,CAAC,aAAa;IAoCrB,cAAc,CAAC,OAAO,cAAe,GAAG,IAAI;IAW5C,OAAO,CAAC,oBAAoB;IAsB5B,8FAA8F;IAC9F,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,IAAI;IAQZ,OAAO,IAAI,IAAI;CAOf"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/session.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,18 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { getTasksLateSidecarPath } from "../session-manager.js";
3
4
  import { truncateHead } from "../tools/truncate.js";
4
5
  import { getTaskUsageRecord } from "../usage-totals.js";
5
6
  import { TASK_RECORD_TYPE, TASK_SNAPSHOT_VERSION } from "./codec.js";
6
7
  import { TaskDelivery } from "./delivery.js";
7
8
  import { TaskContextReminder } from "./reminder.js";
8
9
  import { TaskRuntime } from "./runtime.js";
9
- /** Session persistence and completion delivery; execution supervision stays in TaskRuntime. */
10
+ import { isTaskTerminal } from "./types.js";
11
+ /**
12
+ * Session persistence and completion delivery; execution supervision stays in TaskRuntime.
13
+ * A handed-off task that settles on the current service is persisted here and then enqueued in
14
+ * TaskDelivery, which keeps it retained until the model has seen it.
15
+ */
10
16
  export class TaskSession {
11
17
  constructor(options) {
12
18
  this.enabled = false;
@@ -15,6 +21,7 @@ export class TaskSession {
15
21
  this.waitInterrupt = new AbortController();
16
22
  this.contextReminder = new TaskContextReminder();
17
23
  this.quarantine = [];
24
+ this.changeListeners = new Set();
18
25
  this.options = options;
19
26
  this.delivery = new TaskDelivery({
20
27
  service: () => this.service,
@@ -23,6 +30,31 @@ export class TaskSession {
23
30
  warn: (event, message) => this.warn(event, message),
24
31
  });
25
32
  this._service = this.createService();
33
+ this.delivery.subscribe(() => this.changed());
34
+ }
35
+ /** Called when background tasks, the runtime itself, or delivery state change. */
36
+ subscribe(listener) {
37
+ this.changeListeners.add(listener);
38
+ return () => {
39
+ this.changeListeners.delete(listener);
40
+ };
41
+ }
42
+ changed() {
43
+ for (const listener of [...this.changeListeners]) {
44
+ try {
45
+ listener();
46
+ }
47
+ catch {
48
+ // Observers cannot interrupt task supervision or delivery.
49
+ }
50
+ }
51
+ }
52
+ /** Handed-off tasks of the current runtime that have not settled. */
53
+ runningBackground() {
54
+ const service = this.service;
55
+ if (this.disposed || service.closed)
56
+ return [];
57
+ return service.list().filter((task) => task.mode === "background" && !isTaskTerminal(task.status));
26
58
  }
27
59
  get service() {
28
60
  return this._service;
@@ -33,6 +65,10 @@ export class TaskSession {
33
65
  this.waitInterrupt = new AbortController();
34
66
  previous.abort();
35
67
  }
68
+ /** The active tools changed: availability of background detaching may have too. */
69
+ toolsChanged() {
70
+ this.service.refresh();
71
+ }
36
72
  noteCompaction() {
37
73
  this.contextReminder.compacted();
38
74
  }
@@ -70,12 +106,24 @@ export class TaskSession {
70
106
  branchIds(leafId) {
71
107
  return new Set(leafId === null ? [] : this.options.manager.getBranch(leafId).map((entry) => entry.id));
72
108
  }
109
+ /**
110
+ * Close the current service, release the retention its undelivered completions hold, and
111
+ * drain its executors within the grace period. Lifecycle operations pair this with replaceService().
112
+ */
113
+ shutdown(graceMs) {
114
+ return this.service.shutdown(graceMs);
115
+ }
73
116
  /** Called after the old service's bounded shutdown, while lifecycle delivery is paused. */
74
117
  replaceService() {
75
118
  this.contextReminder.reset();
76
119
  this.service.close();
77
120
  this._service = this.createService();
78
121
  this.delivery.retry();
122
+ this.changed();
123
+ }
124
+ /** Acknowledge that a task's completion reached its consumer by other means than automatic delivery. */
125
+ acknowledgeDelivery(taskId) {
126
+ this.delivery.acknowledge(taskId);
79
127
  }
80
128
  createService() {
81
129
  const { manager, backgroundAllowed } = this.options;
@@ -86,6 +134,7 @@ export class TaskSession {
86
134
  const service = new TaskRuntime({
87
135
  enabled: this.enabled,
88
136
  backgroundAllowed,
137
+ canManageBackground: () => this.options.managementActive?.() ?? true,
89
138
  session: {
90
139
  stoppedByTreeNavigation: (targetId) => this.stoppedByTreeNavigation(targetId, service),
91
140
  },
@@ -93,22 +142,35 @@ export class TaskSession {
93
142
  get interruptSignal() {
94
143
  return interrupts();
95
144
  },
96
- bind: (toolCallId, taskId) => this.delivery.bindWait(service, toolCallId, taskId),
145
+ hold: (taskIds) => this.delivery.hold(taskIds),
146
+ bind: (toolCallId, taskId) => this.delivery.bindWait(toolCallId, taskId),
97
147
  },
98
148
  anchor: () => manager.getLeafId(),
99
149
  onCleanupError: (message) => this.warn("task_cleanup", message),
150
+ onClosed: () => this.delivery.discard(service),
100
151
  onSettled: (task, usage) => {
152
+ const current = !this.disposed && this.service === service;
101
153
  const onBranch = manager.getSessionId() === sessionId &&
102
154
  (task.anchorId === null || manager.getBranch().some((entry) => entry.id === task.anchorId));
103
- if (this.disposed || this.service !== service || !onBranch) {
104
- this.quarantineSettlement({ sessionId, generation, task, usage }, sessionFile);
105
- return;
155
+ try {
156
+ if (current && onBranch)
157
+ this.persistSettlement(task, usage);
158
+ else
159
+ this.quarantineSettlement({ sessionId, generation, task, usage }, sessionFile);
160
+ }
161
+ finally {
162
+ // Delivery still follows a failed write, which the runtime reports. A completion hidden on
163
+ // another branch waits for its branch; a replaced service delivers nothing.
164
+ if (current && task.mode === "background")
165
+ this.delivery.enqueue(service, task.id);
106
166
  }
107
- this.persistSettlement(task, usage);
108
167
  },
109
168
  });
110
169
  this.restoreHistory(service);
111
- service.subscribe(() => this.delivery.schedule());
170
+ service.subscribe(() => {
171
+ this.delivery.schedule();
172
+ this.changed();
173
+ });
112
174
  return service;
113
175
  }
114
176
  restoreHistory(service = this.service) {
@@ -132,8 +194,9 @@ export class TaskSession {
132
194
  if (sessionFile) {
133
195
  try {
134
196
  mkdirSync(dirname(sessionFile), { recursive: true });
135
- appendFileSync(`${sessionFile}.tasks-late.jsonl`, `${JSON.stringify({ version: 1, ...record })}\n`);
136
- persistence = `Saved diagnostic sidecar: ${sessionFile}.tasks-late.jsonl (not included in normal session totals).`;
197
+ const sidecar = getTasksLateSidecarPath(sessionFile);
198
+ appendFileSync(sidecar, `${JSON.stringify({ version: 1, ...record })}\n`);
199
+ persistence = `Saved diagnostic sidecar: ${sidecar} (not included in normal session totals).`;
137
200
  }
138
201
  catch {
139
202
  persistence = `Sidecar write failed. ${persistence}`;
@@ -173,6 +236,7 @@ export class TaskSession {
173
236
  this.interruptWaits();
174
237
  this.service.close();
175
238
  this.delivery.dispose();
239
+ this.changeListeners.clear();
176
240
  }
177
241
  }
178
242
  //# sourceMappingURL=session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/core/tasks/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport type { Usage } from \"@earendil-works/pi-ai/compat\";\nimport type { CustomMessage } from \"../messages.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { truncateHead } from \"../tools/truncate.ts\";\nimport { getTaskUsageRecord } from \"../usage-totals.ts\";\nimport { TASK_RECORD_TYPE, TASK_SNAPSHOT_VERSION } from \"./codec.ts\";\nimport { TaskDelivery } from \"./delivery.ts\";\nimport { TaskContextReminder } from \"./reminder.ts\";\nimport { TaskRuntime } from \"./runtime.ts\";\nimport type { TaskSnapshot } from \"./types.ts\";\n\ninterface TaskSessionOptions {\n\tmanager: SessionManager;\n\tbackgroundAllowed?: boolean;\n\t/** The main session owns input queues, preflight and idle state. */\n\tcanDeliver(): boolean;\n\t/**\n\t * Start a completion turn, or enqueue the message and return waitForPersistence().\n\t * The host owns input routing; this adapter owns the queued delivery's lifetime.\n\t */\n\tdeliver(message: CustomMessage, waitForPersistence: () => Promise<void>): Promise<void>;\n\tonEntry(entry: SessionEntry): void;\n\tonError(event: string, message: string): void;\n}\n\nexport interface QuarantinedTaskSettlement {\n\tsessionId: string;\n\tgeneration: number;\n\ttask: TaskSnapshot;\n\tusage?: Usage;\n}\n\n/** Session persistence and completion delivery; execution supervision stays in TaskRuntime. */\nexport class TaskSession {\n\tprivate readonly options: TaskSessionOptions;\n\tprivate _service: TaskRuntime;\n\tprivate enabled = false;\n\tprivate disposed = false;\n\tprivate generation = 0;\n\tprivate waitInterrupt = new AbortController();\n\tprivate readonly contextReminder = new TaskContextReminder();\n\treadonly delivery: TaskDelivery;\n\tprivate readonly quarantine: QuarantinedTaskSettlement[] = [];\n\n\tconstructor(options: TaskSessionOptions) {\n\t\tthis.options = options;\n\t\tthis.delivery = new TaskDelivery({\n\t\t\tservice: () => this.service,\n\t\t\tcanDeliver: options.canDeliver,\n\t\t\tdeliver: options.deliver,\n\t\t\twarn: (event, message) => this.warn(event, message),\n\t\t});\n\t\tthis._service = this.createService();\n\t}\n\n\tget service(): TaskRuntime {\n\t\treturn this._service;\n\t}\n\n\t/** End only current task waits; the next wait observes a fresh signal. */\n\tinterruptWaits(): void {\n\t\tconst previous = this.waitInterrupt;\n\t\tthis.waitInterrupt = new AbortController();\n\t\tprevious.abort();\n\t}\n\n\tnoteCompaction(): void {\n\t\tthis.contextReminder.compacted();\n\t}\n\n\tprojectContextReminder(messages: AgentMessage[]): AgentMessage[] {\n\t\treturn this.contextReminder.project(this.service, messages);\n\t}\n\n\tnoteAssistantCompleted(): void {\n\t\tthis.contextReminder.assistantCompleted();\n\t}\n\n\tget quarantinedSettlements(): readonly QuarantinedTaskSettlement[] {\n\t\treturn structuredClone(this.quarantine);\n\t}\n\n\tsetEnabled(enabled: boolean): void {\n\t\tthis.enabled = enabled && this.options.backgroundAllowed !== false;\n\t\tthis.service.setEnabled(this.enabled);\n\t}\n\n\t/**\n\t * Running tasks that navigating to `targetId` would cancel, since results are saved on their\n\t * launch branch. Mirrors navigateTree's destination: selecting a user or custom message moves\n\t * the leaf to its parent.\n\t */\n\tstoppedByTreeNavigation(targetId: string, service = this.service): TaskSnapshot[] {\n\t\tconst target = this.options.manager.getEntry(targetId);\n\t\tif (!target) return [];\n\t\tconst leafId =\n\t\t\t(target.type === \"message\" && target.message.role === \"user\") || target.type === \"custom_message\"\n\t\t\t\t? target.parentId\n\t\t\t\t: targetId;\n\t\treturn service.activeOutsideBranch(this.branchIds(leafId));\n\t}\n\n\t/** Cancel running tasks launched outside the branch ending at `leafId`. */\n\tcancelOutsideBranch(leafId: string | null): Promise<void> {\n\t\treturn this.service.cancelOutsideBranch(this.branchIds(leafId));\n\t}\n\n\tprivate branchIds(leafId: string | null): Set<string> {\n\t\treturn new Set(leafId === null ? [] : this.options.manager.getBranch(leafId).map((entry) => entry.id));\n\t}\n\n\t/** Called after the old service's bounded shutdown, while lifecycle delivery is paused. */\n\treplaceService(): void {\n\t\tthis.contextReminder.reset();\n\t\tthis.service.close();\n\t\tthis._service = this.createService();\n\t\tthis.delivery.retry();\n\t}\n\n\tprivate createService(): TaskRuntime {\n\t\tconst { manager, backgroundAllowed } = this.options;\n\t\tconst generation = ++this.generation;\n\t\tconst sessionId = manager.getSessionId();\n\t\tconst sessionFile = manager.getSessionFile();\n\t\tconst interrupts = () => this.waitInterrupt.signal;\n\t\tconst service: TaskRuntime = new TaskRuntime({\n\t\t\tenabled: this.enabled,\n\t\t\tbackgroundAllowed,\n\t\t\tsession: {\n\t\t\t\tstoppedByTreeNavigation: (targetId): TaskSnapshot[] => this.stoppedByTreeNavigation(targetId, service),\n\t\t\t},\n\t\t\twaits: {\n\t\t\t\tget interruptSignal() {\n\t\t\t\t\treturn interrupts();\n\t\t\t\t},\n\t\t\t\tbind: (toolCallId, taskId) => this.delivery.bindWait(service, toolCallId, taskId),\n\t\t\t},\n\t\t\tanchor: () => manager.getLeafId(),\n\t\t\tonCleanupError: (message) => this.warn(\"task_cleanup\", message),\n\t\t\tonSettled: (task, usage) => {\n\t\t\t\tconst onBranch =\n\t\t\t\t\tmanager.getSessionId() === sessionId &&\n\t\t\t\t\t(task.anchorId === null || manager.getBranch().some((entry) => entry.id === task.anchorId));\n\t\t\t\tif (this.disposed || this.service !== service || !onBranch) {\n\t\t\t\t\tthis.quarantineSettlement({ sessionId, generation, task, usage }, sessionFile);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.persistSettlement(task, usage);\n\t\t\t},\n\t\t});\n\t\tthis.restoreHistory(service);\n\t\tservice.subscribe(() => this.delivery.schedule());\n\t\treturn service;\n\t}\n\n\trestoreHistory(service = this.service): void {\n\t\tconst records: unknown[] = [];\n\t\tconst toolResults = new Map<string, AgentToolResult<unknown>[\"content\"]>();\n\t\tfor (const entry of this.options.manager.getBranch()) {\n\t\t\tif (entry.type === \"custom\" && entry.customType === TASK_RECORD_TYPE) records.push(entry.data);\n\t\t\telse if (entry.type === \"message\" && entry.message.role === \"toolResult\")\n\t\t\t\ttoolResults.set(entry.message.toolCallId, entry.message.content);\n\t\t}\n\t\tservice.restoreHistory(records, toolResults);\n\t}\n\n\tprivate quarantineSettlement(record: QuarantinedTaskSettlement, sessionFile: string | undefined): void {\n\t\t// Never move the active leaf to append late usage: JSONL's last entry also\n\t\t// selects its resumed branch. The sidecar is diagnostic evidence only.\n\t\tthis.quarantine.push(structuredClone(record));\n\t\tif (this.quarantine.length > 32) this.quarantine.shift();\n\t\tlet persistence =\n\t\t\t\"Not persisted: only the latest 32 snapshots are retained in quarantinedTaskSettlements; late usage is not accounted in session totals.\";\n\t\tif (sessionFile) {\n\t\t\ttry {\n\t\t\t\tmkdirSync(dirname(sessionFile), { recursive: true });\n\t\t\t\tappendFileSync(`${sessionFile}.tasks-late.jsonl`, `${JSON.stringify({ version: 1, ...record })}\\n`);\n\t\t\t\tpersistence = `Saved diagnostic sidecar: ${sessionFile}.tasks-late.jsonl (not included in normal session totals).`;\n\t\t\t} catch {\n\t\t\t\tpersistence = `Sidecar write failed. ${persistence}`;\n\t\t\t}\n\t\t}\n\t\tthis.warn(\n\t\t\t\"task_settlement_quarantined\",\n\t\t\t`Late background settlement ${record.task.id} was quarantined. ${persistence}`,\n\t\t);\n\t}\n\n\t/** One entry per settlement: the bounded terminal snapshot and its first usage settlement. */\n\tprivate persistSettlement(task: TaskSnapshot, usage: Usage | undefined): void {\n\t\tconst manager = this.options.manager;\n\t\tconst accounted = usage && manager.getEntries().some((entry) => getTaskUsageRecord(entry)?.taskId === task.id);\n\t\t// A foreground result is the tool result the transcript already saves; restoration reads it from there.\n\t\tconst { result: _result, ...metadata } = task;\n\t\tconst saved = task.mode === \"foreground\" ? metadata : task;\n\t\tconst data = JSON.parse(\n\t\t\tJSON.stringify({ version: TASK_SNAPSHOT_VERSION, task: saved, ...(usage && !accounted ? { usage } : {}) }),\n\t\t);\n\t\tconst entry = manager.getEntry(manager.appendCustomEntry(TASK_RECORD_TYPE, data));\n\t\tif (!entry) return;\n\t\ttry {\n\t\t\tthis.options.onEntry(entry);\n\t\t} catch {\n\t\t\t// Observers cannot interrupt settlement or result publication.\n\t\t}\n\t}\n\n\tprivate warn(event: string, message: string): void {\n\t\ttry {\n\t\t\tthis.options.onError(event, truncateHead(message, { maxBytes: 4096, maxLines: 32 }).content);\n\t\t} catch {\n\t\t\t// Diagnostics cannot reject settlement, cleanup or scheduled callbacks.\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.contextReminder.reset();\n\t\tthis.interruptWaits();\n\t\tthis.service.close();\n\t\tthis.delivery.dispose();\n\t}\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/core/tasks/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport type { Usage } from \"@earendil-works/pi-ai/compat\";\nimport type { CustomMessage } from \"../messages.ts\";\nimport { getTasksLateSidecarPath, type SessionEntry, type SessionManager } from \"../session-manager.ts\";\nimport { truncateHead } from \"../tools/truncate.ts\";\nimport { getTaskUsageRecord } from \"../usage-totals.ts\";\nimport { TASK_RECORD_TYPE, TASK_SNAPSHOT_VERSION } from \"./codec.ts\";\nimport { TaskDelivery } from \"./delivery.ts\";\nimport { TaskContextReminder } from \"./reminder.ts\";\nimport { TaskRuntime } from \"./runtime.ts\";\nimport { isTaskTerminal, type TaskSnapshot } from \"./types.ts\";\n\ninterface TaskSessionOptions {\n\tmanager: SessionManager;\n\tbackgroundAllowed?: boolean;\n\t/** The main session owns input queues, preflight and idle state. */\n\tcanDeliver(): boolean;\n\t/** Whether the model-facing management tool is active, which new background work requires. Omitted: no gate. */\n\tmanagementActive?(): boolean;\n\t/**\n\t * Start a completion turn, or enqueue the message and return waitForPersistence().\n\t * The host owns input routing; this adapter owns the queued delivery's lifetime.\n\t */\n\tdeliver(message: CustomMessage, waitForPersistence: () => Promise<void>): Promise<void>;\n\tonEntry(entry: SessionEntry): void;\n\tonError(event: string, message: string): void;\n}\n\nexport interface QuarantinedTaskSettlement {\n\tsessionId: string;\n\tgeneration: number;\n\ttask: TaskSnapshot;\n\tusage?: Usage;\n}\n\n/**\n * Session persistence and completion delivery; execution supervision stays in TaskRuntime.\n * A handed-off task that settles on the current service is persisted here and then enqueued in\n * TaskDelivery, which keeps it retained until the model has seen it.\n */\nexport class TaskSession {\n\tprivate readonly options: TaskSessionOptions;\n\tprivate _service: TaskRuntime;\n\tprivate enabled = false;\n\tprivate disposed = false;\n\tprivate generation = 0;\n\tprivate waitInterrupt = new AbortController();\n\tprivate readonly contextReminder = new TaskContextReminder();\n\treadonly delivery: TaskDelivery;\n\tprivate readonly quarantine: QuarantinedTaskSettlement[] = [];\n\tprivate readonly changeListeners = new Set<() => void>();\n\n\tconstructor(options: TaskSessionOptions) {\n\t\tthis.options = options;\n\t\tthis.delivery = new TaskDelivery({\n\t\t\tservice: () => this.service,\n\t\t\tcanDeliver: options.canDeliver,\n\t\t\tdeliver: options.deliver,\n\t\t\twarn: (event, message) => this.warn(event, message),\n\t\t});\n\t\tthis._service = this.createService();\n\t\tthis.delivery.subscribe(() => this.changed());\n\t}\n\n\t/** Called when background tasks, the runtime itself, or delivery state change. */\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.changeListeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.changeListeners.delete(listener);\n\t\t};\n\t}\n\n\tprivate changed(): void {\n\t\tfor (const listener of [...this.changeListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// Observers cannot interrupt task supervision or delivery.\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Handed-off tasks of the current runtime that have not settled. */\n\trunningBackground(): TaskSnapshot[] {\n\t\tconst service = this.service;\n\t\tif (this.disposed || service.closed) return [];\n\t\treturn service.list().filter((task) => task.mode === \"background\" && !isTaskTerminal(task.status));\n\t}\n\n\tget service(): TaskRuntime {\n\t\treturn this._service;\n\t}\n\n\t/** End only current task waits; the next wait observes a fresh signal. */\n\tinterruptWaits(): void {\n\t\tconst previous = this.waitInterrupt;\n\t\tthis.waitInterrupt = new AbortController();\n\t\tprevious.abort();\n\t}\n\n\t/** The active tools changed: availability of background detaching may have too. */\n\ttoolsChanged(): void {\n\t\tthis.service.refresh();\n\t}\n\n\tnoteCompaction(): void {\n\t\tthis.contextReminder.compacted();\n\t}\n\n\tprojectContextReminder(messages: AgentMessage[]): AgentMessage[] {\n\t\treturn this.contextReminder.project(this.service, messages);\n\t}\n\n\tnoteAssistantCompleted(): void {\n\t\tthis.contextReminder.assistantCompleted();\n\t}\n\n\tget quarantinedSettlements(): readonly QuarantinedTaskSettlement[] {\n\t\treturn structuredClone(this.quarantine);\n\t}\n\n\tsetEnabled(enabled: boolean): void {\n\t\tthis.enabled = enabled && this.options.backgroundAllowed !== false;\n\t\tthis.service.setEnabled(this.enabled);\n\t}\n\n\t/**\n\t * Running tasks that navigating to `targetId` would cancel, since results are saved on their\n\t * launch branch. Mirrors navigateTree's destination: selecting a user or custom message moves\n\t * the leaf to its parent.\n\t */\n\tstoppedByTreeNavigation(targetId: string, service = this.service): TaskSnapshot[] {\n\t\tconst target = this.options.manager.getEntry(targetId);\n\t\tif (!target) return [];\n\t\tconst leafId =\n\t\t\t(target.type === \"message\" && target.message.role === \"user\") || target.type === \"custom_message\"\n\t\t\t\t? target.parentId\n\t\t\t\t: targetId;\n\t\treturn service.activeOutsideBranch(this.branchIds(leafId));\n\t}\n\n\t/** Cancel running tasks launched outside the branch ending at `leafId`. */\n\tcancelOutsideBranch(leafId: string | null): Promise<void> {\n\t\treturn this.service.cancelOutsideBranch(this.branchIds(leafId));\n\t}\n\n\tprivate branchIds(leafId: string | null): Set<string> {\n\t\treturn new Set(leafId === null ? [] : this.options.manager.getBranch(leafId).map((entry) => entry.id));\n\t}\n\n\t/**\n\t * Close the current service, release the retention its undelivered completions hold, and\n\t * drain its executors within the grace period. Lifecycle operations pair this with replaceService().\n\t */\n\tshutdown(graceMs?: number): Promise<void> {\n\t\treturn this.service.shutdown(graceMs);\n\t}\n\n\t/** Called after the old service's bounded shutdown, while lifecycle delivery is paused. */\n\treplaceService(): void {\n\t\tthis.contextReminder.reset();\n\t\tthis.service.close();\n\t\tthis._service = this.createService();\n\t\tthis.delivery.retry();\n\t\tthis.changed();\n\t}\n\n\t/** Acknowledge that a task's completion reached its consumer by other means than automatic delivery. */\n\tacknowledgeDelivery(taskId: string): void {\n\t\tthis.delivery.acknowledge(taskId);\n\t}\n\n\tprivate createService(): TaskRuntime {\n\t\tconst { manager, backgroundAllowed } = this.options;\n\t\tconst generation = ++this.generation;\n\t\tconst sessionId = manager.getSessionId();\n\t\tconst sessionFile = manager.getSessionFile();\n\t\tconst interrupts = () => this.waitInterrupt.signal;\n\t\tconst service: TaskRuntime = new TaskRuntime({\n\t\t\tenabled: this.enabled,\n\t\t\tbackgroundAllowed,\n\t\t\tcanManageBackground: () => this.options.managementActive?.() ?? true,\n\t\t\tsession: {\n\t\t\t\tstoppedByTreeNavigation: (targetId): TaskSnapshot[] => this.stoppedByTreeNavigation(targetId, service),\n\t\t\t},\n\t\t\twaits: {\n\t\t\t\tget interruptSignal() {\n\t\t\t\t\treturn interrupts();\n\t\t\t\t},\n\t\t\t\thold: (taskIds) => this.delivery.hold(taskIds),\n\t\t\t\tbind: (toolCallId, taskId) => this.delivery.bindWait(toolCallId, taskId),\n\t\t\t},\n\t\t\tanchor: () => manager.getLeafId(),\n\t\t\tonCleanupError: (message) => this.warn(\"task_cleanup\", message),\n\t\t\tonClosed: () => this.delivery.discard(service),\n\t\t\tonSettled: (task, usage) => {\n\t\t\t\tconst current = !this.disposed && this.service === service;\n\t\t\t\tconst onBranch =\n\t\t\t\t\tmanager.getSessionId() === sessionId &&\n\t\t\t\t\t(task.anchorId === null || manager.getBranch().some((entry) => entry.id === task.anchorId));\n\t\t\t\ttry {\n\t\t\t\t\tif (current && onBranch) this.persistSettlement(task, usage);\n\t\t\t\t\telse this.quarantineSettlement({ sessionId, generation, task, usage }, sessionFile);\n\t\t\t\t} finally {\n\t\t\t\t\t// Delivery still follows a failed write, which the runtime reports. A completion hidden on\n\t\t\t\t\t// another branch waits for its branch; a replaced service delivers nothing.\n\t\t\t\t\tif (current && task.mode === \"background\") this.delivery.enqueue(service, task.id);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tthis.restoreHistory(service);\n\t\tservice.subscribe(() => {\n\t\t\tthis.delivery.schedule();\n\t\t\tthis.changed();\n\t\t});\n\t\treturn service;\n\t}\n\n\trestoreHistory(service = this.service): void {\n\t\tconst records: unknown[] = [];\n\t\tconst toolResults = new Map<string, AgentToolResult<unknown>[\"content\"]>();\n\t\tfor (const entry of this.options.manager.getBranch()) {\n\t\t\tif (entry.type === \"custom\" && entry.customType === TASK_RECORD_TYPE) records.push(entry.data);\n\t\t\telse if (entry.type === \"message\" && entry.message.role === \"toolResult\")\n\t\t\t\ttoolResults.set(entry.message.toolCallId, entry.message.content);\n\t\t}\n\t\tservice.restoreHistory(records, toolResults);\n\t}\n\n\tprivate quarantineSettlement(record: QuarantinedTaskSettlement, sessionFile: string | undefined): void {\n\t\t// Never move the active leaf to append late usage: JSONL's last entry also\n\t\t// selects its resumed branch. The sidecar is diagnostic evidence only.\n\t\tthis.quarantine.push(structuredClone(record));\n\t\tif (this.quarantine.length > 32) this.quarantine.shift();\n\t\tlet persistence =\n\t\t\t\"Not persisted: only the latest 32 snapshots are retained in quarantinedTaskSettlements; late usage is not accounted in session totals.\";\n\t\tif (sessionFile) {\n\t\t\ttry {\n\t\t\t\tmkdirSync(dirname(sessionFile), { recursive: true });\n\t\t\t\tconst sidecar = getTasksLateSidecarPath(sessionFile);\n\t\t\t\tappendFileSync(sidecar, `${JSON.stringify({ version: 1, ...record })}\\n`);\n\t\t\t\tpersistence = `Saved diagnostic sidecar: ${sidecar} (not included in normal session totals).`;\n\t\t\t} catch {\n\t\t\t\tpersistence = `Sidecar write failed. ${persistence}`;\n\t\t\t}\n\t\t}\n\t\tthis.warn(\n\t\t\t\"task_settlement_quarantined\",\n\t\t\t`Late background settlement ${record.task.id} was quarantined. ${persistence}`,\n\t\t);\n\t}\n\n\t/** One entry per settlement: the bounded terminal snapshot and its first usage settlement. */\n\tprivate persistSettlement(task: TaskSnapshot, usage: Usage | undefined): void {\n\t\tconst manager = this.options.manager;\n\t\tconst accounted = usage && manager.getEntries().some((entry) => getTaskUsageRecord(entry)?.taskId === task.id);\n\t\t// A foreground result is the tool result the transcript already saves; restoration reads it from there.\n\t\tconst { result: _result, ...metadata } = task;\n\t\tconst saved = task.mode === \"foreground\" ? metadata : task;\n\t\tconst data = JSON.parse(\n\t\t\tJSON.stringify({ version: TASK_SNAPSHOT_VERSION, task: saved, ...(usage && !accounted ? { usage } : {}) }),\n\t\t);\n\t\tconst entry = manager.getEntry(manager.appendCustomEntry(TASK_RECORD_TYPE, data));\n\t\tif (!entry) return;\n\t\ttry {\n\t\t\tthis.options.onEntry(entry);\n\t\t} catch {\n\t\t\t// Observers cannot interrupt settlement or result publication.\n\t\t}\n\t}\n\n\tprivate warn(event: string, message: string): void {\n\t\ttry {\n\t\t\tthis.options.onError(event, truncateHead(message, { maxBytes: 4096, maxLines: 32 }).content);\n\t\t} catch {\n\t\t\t// Diagnostics cannot reject settlement, cleanup or scheduled callbacks.\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.contextReminder.reset();\n\t\tthis.interruptWaits();\n\t\tthis.service.close();\n\t\tthis.delivery.dispose();\n\t\tthis.changeListeners.clear();\n\t}\n}\n"]}
@@ -1,6 +1,21 @@
1
1
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
2
  import { TaskRecord } from "./record.ts";
3
- export declare const EXPIRED_OUTPUT = "Output has expired: the managed log was released; showing the stored result.";
3
+ import type { TaskSnapshot } from "./types.ts";
4
+ export interface RestoreLimits {
5
+ /** Room left in the foreground history. */
6
+ foreground: number;
7
+ /** Room left in the background history. */
8
+ background: number;
9
+ /** Room left overall, which protected runtime records can shrink below both histories. */
10
+ total: number;
11
+ }
12
+ /**
13
+ * Which saved terminal snapshots to rehydrate, as a pure function of input order and limits:
14
+ * the newest `endedAt` per ID (a later input wins ties), then the newest few of each history
15
+ * within its room, then the total room, dropping the oldest foreground records before any
16
+ * background one because the transcript already shows foreground output. Returned oldest first.
17
+ */
18
+ export declare function selectRestorable(tasks: readonly TaskSnapshot[], limits: RestoreLimits): TaskSnapshot[];
4
19
  /** Retained snapshots and output leases. This store never owns executable handles. */
5
20
  export declare class TaskStore {
6
21
  private readonly records;
@@ -20,8 +35,6 @@ export declare class TaskStore {
20
35
  unsettled(): TaskRecord[];
21
36
  /** Records on the selected branch. */
22
37
  visible(): TaskRecord[];
23
- /** Settled handoffs eligible for automatic delivery. */
24
- deliverable(): TaskRecord[];
25
38
  /** Records plus output cleanups still running; admission bounds this. */
26
39
  retainedCount(): number;
27
40
  /** Settles once every output cleanup started so far has finished. */
@@ -32,7 +45,7 @@ export declare class TaskStore {
32
45
  */
33
46
  restoreHistory(records: readonly unknown[], toolResults?: ReadonlyMap<string, AgentToolResult<unknown>["content"]>): void;
34
47
  private cleanupOutput;
35
- /** Pending delivery, active reads and pins have their own bounded retention allowance. */
48
+ /** Settled, unpinned records: the bounded history. Pins (reads, the panel, undelivered completions) have their own allowance. */
36
49
  private historyRecords;
37
50
  trim(): void;
38
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,eAAO,MAAM,cAAc,iFAAiF,CAAC;AAY7G,sFAAsF;AACtF,qBAAa,SAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAA4B;gBAE3D,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,OAAO,EACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAO3C,OAAO,KAAK,MAAM,GAEjB;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIvC,mFAAmF;IACnF,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;IAMnC,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI7B,GAAG,IAAI,UAAU,EAAE;IAInB,kEAAkE;IAClE,SAAS,IAAI,UAAU,EAAE;IAIzB,sCAAsC;IACtC,OAAO,IAAI,UAAU,EAAE;IAIvB,wDAAwD;IACxD,WAAW,IAAI,UAAU,EAAE;IAI3B,yEAAyE;IACzE,aAAa,IAAI,MAAM;IAIvB,qEAAqE;IACrE,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;;OAGG;IACH,cAAc,CACb,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAa,GAC/E,IAAI;IA+CP,OAAO,CAAC,aAAa;IAsBrB,0FAA0F;IAC1F,OAAO,CAAC,cAAc;IAItB,IAAI,IAAI,IAAI;CAoBZ"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,EAAkB,UAAU,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,MAAM,WAAW,aAAa;IAC7B,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,EAAE,CA0BtG;AAED,sFAAsF;AACtF,qBAAa,SAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAA4B;gBAE3D,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,OAAO,EACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAO3C,OAAO,KAAK,MAAM,GAEjB;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIvC,mFAAmF;IACnF,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;IAMnC,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI7B,GAAG,IAAI,UAAU,EAAE;IAInB,kEAAkE;IAClE,SAAS,IAAI,UAAU,EAAE;IAIzB,sCAAsC;IACtC,OAAO,IAAI,UAAU,EAAE;IAIvB,yEAAyE;IACzE,aAAa,IAAI,MAAM;IAIvB,qEAAqE;IACrE,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhC;;;OAGG;IACH,cAAc,CACb,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAa,GAC/E,IAAI;IAqCP,OAAO,CAAC,aAAa;IAgBrB,iIAAiI;IACjI,OAAO,CAAC,cAAc;IAItB,IAAI,IAAI,IAAI;CAoBZ"}
@@ -1,16 +1,37 @@
1
1
  import { readVersionedTask } from "./codec.js";
2
2
  import { errorText, resultText } from "./output.js";
3
- import { TaskRecord } from "./record.js";
4
- export const EXPIRED_OUTPUT = "Output has expired: the managed log was released; showing the stored result.";
5
- function oldestTask(tasks) {
6
- let oldest;
3
+ import { EXPIRED_OUTPUT, TaskRecord } from "./record.js";
4
+ const isForeground = (task) => task.mode === "foreground";
5
+ /**
6
+ * Which saved terminal snapshots to rehydrate, as a pure function of input order and limits:
7
+ * the newest `endedAt` per ID (a later input wins ties), then the newest few of each history
8
+ * within its room, then the total room, dropping the oldest foreground records before any
9
+ * background one because the transcript already shows foreground output. Returned oldest first.
10
+ */
11
+ export function selectRestorable(tasks, limits) {
12
+ const newest = new Map();
7
13
  for (const task of tasks) {
8
- if (!oldest || task.endedAt < oldest.endedAt)
9
- oldest = task;
10
- }
11
- return oldest;
14
+ const previous = newest.get(task.id);
15
+ if (previous && previous.endedAt > task.endedAt)
16
+ continue;
17
+ // Re-inserting keeps arrival order meaningful for ties: the later input is the newer one.
18
+ newest.delete(task.id);
19
+ newest.set(task.id, task);
20
+ }
21
+ // A stable sort keeps arrival order among equal times, so the oldest is the first of its ties.
22
+ const oldestFirst = [...newest.values()].sort((left, right) => left.endedAt - right.endedAt);
23
+ const newestOf = (group, room) => group.slice(Math.max(0, group.length - room));
24
+ const keep = new Set([
25
+ ...newestOf(oldestFirst.filter(isForeground), limits.foreground),
26
+ ...newestOf(oldestFirst.filter((task) => !isForeground(task)), limits.background),
27
+ ]);
28
+ const kept = oldestFirst.filter((task) => keep.has(task));
29
+ const dropped = Math.max(0, kept.length - limits.total);
30
+ if (dropped === 0)
31
+ return kept;
32
+ const doomed = new Set([...kept.filter(isForeground), ...kept.filter((task) => !isForeground(task))].slice(0, dropped));
33
+ return kept.filter((task) => !doomed.has(task));
12
34
  }
13
- const isForeground = (task) => task.mode === "foreground";
14
35
  /** Retained snapshots and output leases. This store never owns executable handles. */
15
36
  export class TaskStore {
16
37
  constructor(maxHistory, maxRetained, isClosed, onCleanupError) {
@@ -45,10 +66,6 @@ export class TaskStore {
45
66
  visible() {
46
67
  return this.all().filter((record) => record.visible);
47
68
  }
48
- /** Settled handoffs eligible for automatic delivery. */
49
- deliverable() {
50
- return this.all().filter((record) => record.deliverable);
51
- }
52
69
  /** Records plus output cleanups still running; admission bounds this. */
53
70
  retainedCount() {
54
71
  return this.records.size + this.cleanups.size;
@@ -70,7 +87,7 @@ export class TaskStore {
70
87
  const foregroundCapacity = Math.max(0, this.maxHistory - foreground);
71
88
  const backgroundCapacity = Math.max(0, this.maxHistory - (history.length - foreground));
72
89
  const capacity = Math.max(0, Math.min(foregroundCapacity + backgroundCapacity, this.maxRetained - this.retainedCount()));
73
- const newest = new Map();
90
+ const saved = [];
74
91
  for (const value of records) {
75
92
  const task = readVersionedTask(value);
76
93
  if (!task)
@@ -78,31 +95,17 @@ export class TaskStore {
78
95
  const existing = this.records.get(task.id);
79
96
  if (existing) {
80
97
  if (existing.settled)
81
- existing.visible = true;
98
+ existing.setVisible(true);
82
99
  continue;
83
100
  }
84
- if (capacity === 0)
85
- continue;
86
- const previous = newest.get(task.id);
87
- if (previous && previous.endedAt > task.endedAt)
88
- continue;
89
- newest.delete(task.id);
90
- const inline = isForeground(task);
91
- const limit = inline ? foregroundCapacity : backgroundCapacity;
92
- if (limit === 0)
93
- continue;
94
- newest.set(task.id, task);
95
- const group = [...newest.values()].filter((candidate) => isForeground(candidate) === inline);
96
- if (group.length > limit)
97
- newest.delete(oldestTask(group).id);
98
- if (newest.size > capacity) {
99
- // Protected runtime records can leave less room than both histories allow.
100
- // Restore background results before foreground ones, which the transcript shows.
101
- const inlineTasks = [...newest.values()].filter(isForeground);
102
- newest.delete(oldestTask(inlineTasks.length ? inlineTasks : newest.values()).id);
103
- }
101
+ saved.push(task);
104
102
  }
105
- for (const task of [...newest.values()].sort((a, b) => a.endedAt - b.endedAt)) {
103
+ const selected = selectRestorable(saved, {
104
+ foreground: foregroundCapacity,
105
+ background: backgroundCapacity,
106
+ total: capacity,
107
+ });
108
+ for (const task of selected) {
106
109
  const content = toolResults.get(task.toolCallId);
107
110
  if (!task.result && content)
108
111
  task.result = resultText({ content, details: undefined });
@@ -113,15 +116,9 @@ export class TaskStore {
113
116
  }
114
117
  }
115
118
  cleanupOutput(record) {
116
- if (!record.settled || record.pins || record.waiters.size || !record.cleanup)
119
+ const cleanup = record.expireOutput();
120
+ if (!cleanup)
117
121
  return;
118
- const cleanup = record.cleanup;
119
- record.cleanup = undefined;
120
- // Snapshots retain final bounded text; expired files are never needed to render history.
121
- record.update((task) => {
122
- task.outputPath = undefined;
123
- });
124
- record.readError = EXPIRED_OUTPUT;
125
122
  const pending = Promise.resolve()
126
123
  .then(cleanup)
127
124
  .catch((error) => {
@@ -135,7 +132,7 @@ export class TaskStore {
135
132
  .finally(() => this.cleanups.delete(pending));
136
133
  this.cleanups.add(pending);
137
134
  }
138
- /** Pending delivery, active reads and pins have their own bounded retention allowance. */
135
+ /** Settled, unpinned records: the bounded history. Pins (reads, the panel, undelivered completions) have their own allowance. */
139
136
  historyRecords() {
140
137
  return this.all().filter((record) => record.inHistory);
141
138
  }
@@ -145,10 +142,10 @@ export class TaskStore {
145
142
  this.cleanupOutput(record);
146
143
  return;
147
144
  }
148
- // Undelivered notifications and claims are never evicted. Admission bounds all retention.
145
+ // Pinned records, such as undelivered completions, are never evicted. Admission bounds all retention.
149
146
  const history = this.historyRecords();
150
- // Delivered history can be restored from branch snapshots; hidden rows must
151
- // not occupy the current branch's history budget. Pending results stay owned.
147
+ // History can be restored from branch snapshots; hidden rows must not occupy the
148
+ // current branch's history budget. Pinned results stay owned.
152
149
  const expired = history.filter((record) => !record.visible);
153
150
  for (const inline of [false, true]) {
154
151
  const group = history.filter((record) => record.visible && isForeground(record.task) === inline);
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/tasks/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { readVersionedTask } from \"./codec.ts\";\nimport { errorText, resultText } from \"./output.ts\";\nimport { TaskRecord } from \"./record.ts\";\nimport type { TaskSnapshot } from \"./types.ts\";\n\nexport const EXPIRED_OUTPUT = \"Output has expired: the managed log was released; showing the stored result.\";\n\nfunction oldestTask(tasks: Iterable<TaskSnapshot>): TaskSnapshot | undefined {\n\tlet oldest: TaskSnapshot | undefined;\n\tfor (const task of tasks) {\n\t\tif (!oldest || task.endedAt! < oldest.endedAt!) oldest = task;\n\t}\n\treturn oldest;\n}\n\nconst isForeground = (task: TaskSnapshot) => task.mode === \"foreground\";\n\n/** Retained snapshots and output leases. This store never owns executable handles. */\nexport class TaskStore {\n\tprivate readonly records = new Map<string, TaskRecord>();\n\tprivate readonly cleanups = new Set<Promise<void>>();\n\tprivate readonly maxHistory: number;\n\tprivate readonly maxRetained: number;\n\tprivate readonly isClosed: () => boolean;\n\tprivate readonly onCleanupError?: (message: string) => void;\n\tconstructor(\n\t\tmaxHistory: number,\n\t\tmaxRetained: number,\n\t\tisClosed: () => boolean,\n\t\tonCleanupError?: (message: string) => void,\n\t) {\n\t\tthis.maxHistory = maxHistory;\n\t\tthis.maxRetained = maxRetained;\n\t\tthis.isClosed = isClosed;\n\t\tthis.onCleanupError = onCleanupError;\n\t}\n\tprivate get closed(): boolean {\n\t\treturn this.isClosed();\n\t}\n\n\tget(id: string): TaskRecord | undefined {\n\t\treturn this.records.get(id);\n\t}\n\n\t/** Records whose ID, or whose ID without its kind prefix, starts with `prefix`. */\n\tmatch(prefix: string): TaskRecord[] {\n\t\treturn this.all().filter(\n\t\t\t({ task }) => task.id.startsWith(prefix) || task.id.slice(task.kind.length + 1).startsWith(prefix),\n\t\t);\n\t}\n\n\tadd(record: TaskRecord): void {\n\t\tthis.records.set(record.task.id, record);\n\t}\n\n\tall(): TaskRecord[] {\n\t\treturn [...this.records.values()];\n\t}\n\n\t/** Records whose execution has not settled, queued or running. */\n\tunsettled(): TaskRecord[] {\n\t\treturn this.all().filter((record) => !record.settled);\n\t}\n\n\t/** Records on the selected branch. */\n\tvisible(): TaskRecord[] {\n\t\treturn this.all().filter((record) => record.visible);\n\t}\n\n\t/** Settled handoffs eligible for automatic delivery. */\n\tdeliverable(): TaskRecord[] {\n\t\treturn this.all().filter((record) => record.deliverable);\n\t}\n\n\t/** Records plus output cleanups still running; admission bounds this. */\n\tretainedCount(): number {\n\t\treturn this.records.size + this.cleanups.size;\n\t}\n\n\t/** Settles once every output cleanup started so far has finished. */\n\tcleanupsDone(): Promise<unknown> {\n\t\treturn Promise.all(this.cleanups);\n\t}\n\n\t/**\n\t * Foreground records save no output of their own: their tool result already holds it,\n\t * so `toolResults` maps each tool call ID on the branch to that content.\n\t */\n\trestoreHistory(\n\t\trecords: readonly unknown[],\n\t\ttoolResults: ReadonlyMap<string, AgentToolResult<unknown>[\"content\"]> = new Map(),\n\t): void {\n\t\tif (this.closed) return;\n\t\t// Restoration must not evict runtime-owned records or run their cleanup callbacks.\n\t\tconst history = this.historyRecords();\n\t\tconst foreground = history.filter((record) => isForeground(record.task)).length;\n\t\tconst foregroundCapacity = Math.max(0, this.maxHistory - foreground);\n\t\tconst backgroundCapacity = Math.max(0, this.maxHistory - (history.length - foreground));\n\t\tconst capacity = Math.max(\n\t\t\t0,\n\t\t\tMath.min(foregroundCapacity + backgroundCapacity, this.maxRetained - this.retainedCount()),\n\t\t);\n\t\tconst newest = new Map<string, TaskSnapshot>();\n\t\tfor (const value of records) {\n\t\t\tconst task = readVersionedTask(value);\n\t\t\tif (!task) continue;\n\t\t\tconst existing = this.records.get(task.id);\n\t\t\tif (existing) {\n\t\t\t\tif (existing.settled) existing.visible = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (capacity === 0) continue;\n\t\t\tconst previous = newest.get(task.id);\n\t\t\tif (previous && previous.endedAt! > task.endedAt!) continue;\n\t\t\tnewest.delete(task.id);\n\t\t\tconst inline = isForeground(task);\n\t\t\tconst limit = inline ? foregroundCapacity : backgroundCapacity;\n\t\t\tif (limit === 0) continue;\n\t\t\tnewest.set(task.id, task);\n\t\t\tconst group = [...newest.values()].filter((candidate) => isForeground(candidate) === inline);\n\t\t\tif (group.length > limit) newest.delete(oldestTask(group)!.id);\n\t\t\tif (newest.size > capacity) {\n\t\t\t\t// Protected runtime records can leave less room than both histories allow.\n\t\t\t\t// Restore background results before foreground ones, which the transcript shows.\n\t\t\t\tconst inlineTasks = [...newest.values()].filter(isForeground);\n\t\t\t\tnewest.delete(oldestTask(inlineTasks.length ? inlineTasks : newest.values())!.id);\n\t\t\t}\n\t\t}\n\t\tfor (const task of [...newest.values()].sort((a, b) => a.endedAt! - b.endedAt!)) {\n\t\t\tconst content = toolResults.get(task.toolCallId);\n\t\t\tif (!task.result && content) task.result = resultText({ content, details: undefined });\n\t\t\t// Logs never outlive the runtime that wrote them.\n\t\t\tconst expired = task.outputPath !== undefined;\n\t\t\ttask.outputPath = undefined;\n\t\t\tthis.add(TaskRecord.restored(task, expired ? EXPIRED_OUTPUT : undefined));\n\t\t}\n\t}\n\n\tprivate cleanupOutput(record: TaskRecord): void {\n\t\tif (!record.settled || record.pins || record.waiters.size || !record.cleanup) return;\n\t\tconst cleanup = record.cleanup;\n\t\trecord.cleanup = undefined;\n\t\t// Snapshots retain final bounded text; expired files are never needed to render history.\n\t\trecord.update((task) => {\n\t\t\ttask.outputPath = undefined;\n\t\t});\n\t\trecord.readError = EXPIRED_OUTPUT;\n\t\tconst pending = Promise.resolve()\n\t\t\t.then(cleanup)\n\t\t\t.catch((error: unknown) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.onCleanupError?.(errorText(error, \"Task cleanup failed\"));\n\t\t\t\t} catch {\n\t\t\t\t\t/* Cleanup/reporting is best effort, never an unhandled rejection. */\n\t\t\t\t}\n\t\t\t})\n\t\t\t.finally(() => this.cleanups.delete(pending));\n\t\tthis.cleanups.add(pending);\n\t}\n\n\t/** Pending delivery, active reads and pins have their own bounded retention allowance. */\n\tprivate historyRecords(): TaskRecord[] {\n\t\treturn this.all().filter((record) => record.inHistory);\n\t}\n\n\ttrim(): void {\n\t\tif (this.closed) {\n\t\t\tfor (const record of this.records.values()) this.cleanupOutput(record);\n\t\t\treturn;\n\t\t}\n\t\t// Undelivered notifications and claims are never evicted. Admission bounds all retention.\n\t\tconst history = this.historyRecords();\n\t\t// Delivered history can be restored from branch snapshots; hidden rows must\n\t\t// not occupy the current branch's history budget. Pending results stay owned.\n\t\tconst expired = history.filter((record) => !record.visible);\n\t\tfor (const inline of [false, true]) {\n\t\t\tconst group = history.filter((record) => record.visible && isForeground(record.task) === inline);\n\t\t\tgroup.sort((left, right) => (left.task.endedAt ?? 0) - (right.task.endedAt ?? 0));\n\t\t\texpired.push(...group.slice(0, Math.max(0, group.length - this.maxHistory)));\n\t\t}\n\t\tfor (const record of expired) {\n\t\t\tthis.records.delete(record.task.id);\n\t\t\tthis.cleanupOutput(record);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/tasks/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { readVersionedTask } from \"./codec.ts\";\nimport { errorText, resultText } from \"./output.ts\";\nimport { EXPIRED_OUTPUT, TaskRecord } from \"./record.ts\";\nimport type { TaskSnapshot } from \"./types.ts\";\n\nconst isForeground = (task: TaskSnapshot) => task.mode === \"foreground\";\n\nexport interface RestoreLimits {\n\t/** Room left in the foreground history. */\n\tforeground: number;\n\t/** Room left in the background history. */\n\tbackground: number;\n\t/** Room left overall, which protected runtime records can shrink below both histories. */\n\ttotal: number;\n}\n\n/**\n * Which saved terminal snapshots to rehydrate, as a pure function of input order and limits:\n * the newest `endedAt` per ID (a later input wins ties), then the newest few of each history\n * within its room, then the total room, dropping the oldest foreground records before any\n * background one because the transcript already shows foreground output. Returned oldest first.\n */\nexport function selectRestorable(tasks: readonly TaskSnapshot[], limits: RestoreLimits): TaskSnapshot[] {\n\tconst newest = new Map<string, TaskSnapshot>();\n\tfor (const task of tasks) {\n\t\tconst previous = newest.get(task.id);\n\t\tif (previous && previous.endedAt! > task.endedAt!) continue;\n\t\t// Re-inserting keeps arrival order meaningful for ties: the later input is the newer one.\n\t\tnewest.delete(task.id);\n\t\tnewest.set(task.id, task);\n\t}\n\t// A stable sort keeps arrival order among equal times, so the oldest is the first of its ties.\n\tconst oldestFirst = [...newest.values()].sort((left, right) => left.endedAt! - right.endedAt!);\n\tconst newestOf = (group: TaskSnapshot[], room: number) => group.slice(Math.max(0, group.length - room));\n\tconst keep = new Set([\n\t\t...newestOf(oldestFirst.filter(isForeground), limits.foreground),\n\t\t...newestOf(\n\t\t\toldestFirst.filter((task) => !isForeground(task)),\n\t\t\tlimits.background,\n\t\t),\n\t]);\n\tconst kept = oldestFirst.filter((task) => keep.has(task));\n\tconst dropped = Math.max(0, kept.length - limits.total);\n\tif (dropped === 0) return kept;\n\tconst doomed = new Set(\n\t\t[...kept.filter(isForeground), ...kept.filter((task) => !isForeground(task))].slice(0, dropped),\n\t);\n\treturn kept.filter((task) => !doomed.has(task));\n}\n\n/** Retained snapshots and output leases. This store never owns executable handles. */\nexport class TaskStore {\n\tprivate readonly records = new Map<string, TaskRecord>();\n\tprivate readonly cleanups = new Set<Promise<void>>();\n\tprivate readonly maxHistory: number;\n\tprivate readonly maxRetained: number;\n\tprivate readonly isClosed: () => boolean;\n\tprivate readonly onCleanupError?: (message: string) => void;\n\tconstructor(\n\t\tmaxHistory: number,\n\t\tmaxRetained: number,\n\t\tisClosed: () => boolean,\n\t\tonCleanupError?: (message: string) => void,\n\t) {\n\t\tthis.maxHistory = maxHistory;\n\t\tthis.maxRetained = maxRetained;\n\t\tthis.isClosed = isClosed;\n\t\tthis.onCleanupError = onCleanupError;\n\t}\n\tprivate get closed(): boolean {\n\t\treturn this.isClosed();\n\t}\n\n\tget(id: string): TaskRecord | undefined {\n\t\treturn this.records.get(id);\n\t}\n\n\t/** Records whose ID, or whose ID without its kind prefix, starts with `prefix`. */\n\tmatch(prefix: string): TaskRecord[] {\n\t\treturn this.all().filter(\n\t\t\t({ task }) => task.id.startsWith(prefix) || task.id.slice(task.kind.length + 1).startsWith(prefix),\n\t\t);\n\t}\n\n\tadd(record: TaskRecord): void {\n\t\tthis.records.set(record.task.id, record);\n\t}\n\n\tall(): TaskRecord[] {\n\t\treturn [...this.records.values()];\n\t}\n\n\t/** Records whose execution has not settled, queued or running. */\n\tunsettled(): TaskRecord[] {\n\t\treturn this.all().filter((record) => !record.settled);\n\t}\n\n\t/** Records on the selected branch. */\n\tvisible(): TaskRecord[] {\n\t\treturn this.all().filter((record) => record.visible);\n\t}\n\n\t/** Records plus output cleanups still running; admission bounds this. */\n\tretainedCount(): number {\n\t\treturn this.records.size + this.cleanups.size;\n\t}\n\n\t/** Settles once every output cleanup started so far has finished. */\n\tcleanupsDone(): Promise<unknown> {\n\t\treturn Promise.all(this.cleanups);\n\t}\n\n\t/**\n\t * Foreground records save no output of their own: their tool result already holds it,\n\t * so `toolResults` maps each tool call ID on the branch to that content.\n\t */\n\trestoreHistory(\n\t\trecords: readonly unknown[],\n\t\ttoolResults: ReadonlyMap<string, AgentToolResult<unknown>[\"content\"]> = new Map(),\n\t): void {\n\t\tif (this.closed) return;\n\t\t// Restoration must not evict runtime-owned records or run their cleanup callbacks.\n\t\tconst history = this.historyRecords();\n\t\tconst foreground = history.filter((record) => isForeground(record.task)).length;\n\t\tconst foregroundCapacity = Math.max(0, this.maxHistory - foreground);\n\t\tconst backgroundCapacity = Math.max(0, this.maxHistory - (history.length - foreground));\n\t\tconst capacity = Math.max(\n\t\t\t0,\n\t\t\tMath.min(foregroundCapacity + backgroundCapacity, this.maxRetained - this.retainedCount()),\n\t\t);\n\t\tconst saved: TaskSnapshot[] = [];\n\t\tfor (const value of records) {\n\t\t\tconst task = readVersionedTask(value);\n\t\t\tif (!task) continue;\n\t\t\tconst existing = this.records.get(task.id);\n\t\t\tif (existing) {\n\t\t\t\tif (existing.settled) existing.setVisible(true);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsaved.push(task);\n\t\t}\n\t\tconst selected = selectRestorable(saved, {\n\t\t\tforeground: foregroundCapacity,\n\t\t\tbackground: backgroundCapacity,\n\t\t\ttotal: capacity,\n\t\t});\n\t\tfor (const task of selected) {\n\t\t\tconst content = toolResults.get(task.toolCallId);\n\t\t\tif (!task.result && content) task.result = resultText({ content, details: undefined });\n\t\t\t// Logs never outlive the runtime that wrote them.\n\t\t\tconst expired = task.outputPath !== undefined;\n\t\t\ttask.outputPath = undefined;\n\t\t\tthis.add(TaskRecord.restored(task, expired ? EXPIRED_OUTPUT : undefined));\n\t\t}\n\t}\n\n\tprivate cleanupOutput(record: TaskRecord): void {\n\t\tconst cleanup = record.expireOutput();\n\t\tif (!cleanup) return;\n\t\tconst pending = Promise.resolve()\n\t\t\t.then(cleanup)\n\t\t\t.catch((error: unknown) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.onCleanupError?.(errorText(error, \"Task cleanup failed\"));\n\t\t\t\t} catch {\n\t\t\t\t\t/* Cleanup/reporting is best effort, never an unhandled rejection. */\n\t\t\t\t}\n\t\t\t})\n\t\t\t.finally(() => this.cleanups.delete(pending));\n\t\tthis.cleanups.add(pending);\n\t}\n\n\t/** Settled, unpinned records: the bounded history. Pins (reads, the panel, undelivered completions) have their own allowance. */\n\tprivate historyRecords(): TaskRecord[] {\n\t\treturn this.all().filter((record) => record.inHistory);\n\t}\n\n\ttrim(): void {\n\t\tif (this.closed) {\n\t\t\tfor (const record of this.records.values()) this.cleanupOutput(record);\n\t\t\treturn;\n\t\t}\n\t\t// Pinned records, such as undelivered completions, are never evicted. Admission bounds all retention.\n\t\tconst history = this.historyRecords();\n\t\t// History can be restored from branch snapshots; hidden rows must not occupy the\n\t\t// current branch's history budget. Pinned results stay owned.\n\t\tconst expired = history.filter((record) => !record.visible);\n\t\tfor (const inline of [false, true]) {\n\t\t\tconst group = history.filter((record) => record.visible && isForeground(record.task) === inline);\n\t\t\tgroup.sort((left, right) => (left.task.endedAt ?? 0) - (right.task.endedAt ?? 0));\n\t\t\texpired.push(...group.slice(0, Math.max(0, group.length - this.maxHistory)));\n\t\t}\n\t\tfor (const record of expired) {\n\t\t\tthis.records.delete(record.task.id);\n\t\t\tthis.cleanupOutput(record);\n\t\t}\n\t}\n}\n"]}