@plurnk/plurnk-service 1.0.4 → 1.0.6

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 (215) hide show
  1. package/.env.defaults +55 -31
  2. package/INSTALL.md +3 -3
  3. package/README.md +4 -4
  4. package/SPEC.md +205 -193
  5. package/dist/Paths.js +1 -1
  6. package/dist/Paths.js.map +1 -1
  7. package/dist/core/ChannelWrite.d.ts +18 -18
  8. package/dist/core/ChannelWrite.d.ts.map +1 -1
  9. package/dist/core/ChannelWrite.js +17 -17
  10. package/dist/core/ChannelWrite.js.map +1 -1
  11. package/dist/core/ChannelWrite.sql +11 -11
  12. package/dist/core/Dispatcher.d.ts +11 -16
  13. package/dist/core/Dispatcher.d.ts.map +1 -1
  14. package/dist/core/Dispatcher.js +122 -125
  15. package/dist/core/Dispatcher.js.map +1 -1
  16. package/dist/core/Engine.d.ts +20 -23
  17. package/dist/core/Engine.d.ts.map +1 -1
  18. package/dist/core/Engine.js +213 -210
  19. package/dist/core/Engine.js.map +1 -1
  20. package/dist/core/Engine.sql +108 -130
  21. package/dist/core/PacketBuilder.d.ts +10 -11
  22. package/dist/core/PacketBuilder.d.ts.map +1 -1
  23. package/dist/core/PacketBuilder.js +204 -128
  24. package/dist/core/PacketBuilder.js.map +1 -1
  25. package/dist/core/ProposalLifecycle.d.ts +8 -8
  26. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  27. package/dist/core/ProposalLifecycle.js +10 -10
  28. package/dist/core/ProposalLifecycle.js.map +1 -1
  29. package/dist/core/ProviderInstantiate.d.ts +1 -0
  30. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  31. package/dist/core/ProviderInstantiate.js +9 -3
  32. package/dist/core/ProviderInstantiate.js.map +1 -1
  33. package/dist/core/SchemeRegistry.d.ts +4 -1
  34. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  35. package/dist/core/SchemeRegistry.js +18 -9
  36. package/dist/core/SchemeRegistry.js.map +1 -1
  37. package/dist/core/TelemetryChannel.d.ts +5 -5
  38. package/dist/core/TelemetryChannel.d.ts.map +1 -1
  39. package/dist/core/TelemetryChannel.js +9 -9
  40. package/dist/core/TelemetryChannel.js.map +1 -1
  41. package/dist/core/caps/CapsResolve.js +2 -2
  42. package/dist/core/caps/CapsResolve.js.map +1 -1
  43. package/dist/core/caps/DbNotifyCaps.js +2 -2
  44. package/dist/core/caps/DbNotifyCaps.js.map +1 -1
  45. package/dist/core/caps/DbSubscriptionCaps.js +7 -7
  46. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  47. package/dist/core/caps/SchemeCtxImpl.d.ts +2 -2
  48. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  49. package/dist/core/caps/SchemeCtxImpl.js +4 -4
  50. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  51. package/dist/core/content-hash.d.ts +2 -0
  52. package/dist/core/content-hash.d.ts.map +1 -0
  53. package/dist/core/content-hash.js +6 -0
  54. package/dist/core/content-hash.js.map +1 -0
  55. package/dist/core/fork.d.ts +1 -1
  56. package/dist/core/fork.d.ts.map +1 -1
  57. package/dist/core/fork.js +31 -26
  58. package/dist/core/fork.js.map +1 -1
  59. package/dist/core/fork.sql +41 -32
  60. package/dist/core/git-env.d.ts.map +1 -1
  61. package/dist/core/git-env.js +23 -7
  62. package/dist/core/git-env.js.map +1 -1
  63. package/dist/core/git-iso.d.ts +9 -0
  64. package/dist/core/git-iso.d.ts.map +1 -0
  65. package/dist/core/git-iso.js +227 -0
  66. package/dist/core/git-iso.js.map +1 -0
  67. package/dist/core/git-membership.d.ts +2 -2
  68. package/dist/core/git-membership.d.ts.map +1 -1
  69. package/dist/core/git-membership.js +70 -50
  70. package/dist/core/git-membership.js.map +1 -1
  71. package/dist/core/git-state.d.ts +1 -1
  72. package/dist/core/git-state.d.ts.map +1 -1
  73. package/dist/core/git-state.js +21 -11
  74. package/dist/core/git-state.js.map +1 -1
  75. package/dist/core/packet-wire.d.ts.map +1 -1
  76. package/dist/core/packet-wire.js +81 -22
  77. package/dist/core/packet-wire.js.map +1 -1
  78. package/dist/core/plurnk-uri.d.ts +2 -2
  79. package/dist/core/plurnk-uri.d.ts.map +1 -1
  80. package/dist/core/plurnk-uri.js +19 -12
  81. package/dist/core/plurnk-uri.js.map +1 -1
  82. package/dist/core/run-ops.sql +26 -26
  83. package/dist/core/scheme-types.d.ts +6 -11
  84. package/dist/core/scheme-types.d.ts.map +1 -1
  85. package/dist/core/search-gate.js +0 -0
  86. package/dist/core/search-gate.js.map +1 -1
  87. package/dist/core/token-ruler.d.ts +2 -0
  88. package/dist/core/token-ruler.d.ts.map +1 -0
  89. package/dist/core/token-ruler.js +18 -0
  90. package/dist/core/token-ruler.js.map +1 -0
  91. package/dist/core/worker-cap.d.ts +8 -0
  92. package/dist/core/worker-cap.d.ts.map +1 -0
  93. package/dist/core/worker-cap.js +20 -0
  94. package/dist/core/worker-cap.js.map +1 -0
  95. package/dist/core/{session-settings.d.ts → workspace-settings.d.ts} +5 -5
  96. package/dist/core/workspace-settings.d.ts.map +1 -0
  97. package/dist/core/{session-settings.js → workspace-settings.js} +13 -13
  98. package/dist/core/workspace-settings.js.map +1 -0
  99. package/dist/digest/Digest.d.ts +3 -3
  100. package/dist/digest/Digest.d.ts.map +1 -1
  101. package/dist/digest/Digest.js +79 -73
  102. package/dist/digest/Digest.js.map +1 -1
  103. package/dist/digest/digest.sql +18 -18
  104. package/dist/schemes/Exec.d.ts +9 -2
  105. package/dist/schemes/Exec.d.ts.map +1 -1
  106. package/dist/schemes/Exec.js +121 -68
  107. package/dist/schemes/Exec.js.map +1 -1
  108. package/dist/schemes/ExecOutputScheme.js +3 -3
  109. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  110. package/dist/schemes/File.js +23 -23
  111. package/dist/schemes/File.js.map +1 -1
  112. package/dist/schemes/Known.js +5 -5
  113. package/dist/schemes/Known.js.map +1 -1
  114. package/dist/schemes/Log.d.ts.map +1 -1
  115. package/dist/schemes/Log.js +63 -14
  116. package/dist/schemes/Log.js.map +1 -1
  117. package/dist/schemes/Log.sql +49 -8
  118. package/dist/schemes/Plurnk.js +5 -5
  119. package/dist/schemes/Plurnk.js.map +1 -1
  120. package/dist/schemes/Skill.js +5 -5
  121. package/dist/schemes/Skill.js.map +1 -1
  122. package/dist/schemes/Unknown.js +5 -5
  123. package/dist/schemes/Unknown.js.map +1 -1
  124. package/dist/schemes/{Run.d.ts → Worker.d.ts} +2 -2
  125. package/dist/schemes/Worker.d.ts.map +1 -0
  126. package/dist/schemes/{Run.js → Worker.js} +67 -67
  127. package/dist/schemes/Worker.js.map +1 -0
  128. package/dist/schemes/_entry-crud.js +11 -11
  129. package/dist/schemes/_entry-crud.js.map +1 -1
  130. package/dist/schemes/_entry-crud.sql +29 -29
  131. package/dist/schemes/_entry-find.d.ts +2 -1
  132. package/dist/schemes/_entry-find.d.ts.map +1 -1
  133. package/dist/schemes/_entry-find.js +31 -19
  134. package/dist/schemes/_entry-find.js.map +1 -1
  135. package/dist/schemes/_entry-find.sql +9 -9
  136. package/dist/schemes/_entry-graph.d.ts +2 -2
  137. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  138. package/dist/schemes/_entry-graph.js +18 -18
  139. package/dist/schemes/_entry-graph.js.map +1 -1
  140. package/dist/schemes/_entry-graph.sql +8 -8
  141. package/dist/schemes/_entry-manifest.d.ts +1 -1
  142. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  143. package/dist/schemes/_entry-manifest.js +74 -50
  144. package/dist/schemes/_entry-manifest.js.map +1 -1
  145. package/dist/schemes/_entry-ops.d.ts +4 -4
  146. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  147. package/dist/schemes/_entry-ops.js +25 -25
  148. package/dist/schemes/_entry-ops.js.map +1 -1
  149. package/dist/schemes/_entry-ops.sql +9 -9
  150. package/dist/schemes/_entry-semantic.d.ts +2 -1
  151. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  152. package/dist/schemes/_entry-semantic.js +23 -4
  153. package/dist/schemes/_entry-semantic.js.map +1 -1
  154. package/dist/schemes/_entry-semantic.sql +18 -4
  155. package/dist/schemes/_entry-send.d.ts +1 -1
  156. package/dist/schemes/_entry-send.d.ts.map +1 -1
  157. package/dist/schemes/_entry-send.js +6 -6
  158. package/dist/schemes/_entry-send.js.map +1 -1
  159. package/dist/schemes/exec-abort.js +1 -1
  160. package/dist/schemes/exec-abort.js.map +1 -1
  161. package/dist/server/Daemon.d.ts +50 -48
  162. package/dist/server/Daemon.d.ts.map +1 -1
  163. package/dist/server/Daemon.js +307 -297
  164. package/dist/server/Daemon.js.map +1 -1
  165. package/dist/server/client-input.js +20 -20
  166. package/dist/server/client-input.js.map +1 -1
  167. package/dist/server/dispatch-as-plurnk.d.ts +1 -1
  168. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  169. package/dist/server/dispatch-as-plurnk.js +7 -7
  170. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  171. package/dist/server/drain.sql +27 -27
  172. package/dist/server/envelope.d.ts +20 -20
  173. package/dist/server/envelope.d.ts.map +1 -1
  174. package/dist/server/envelope.js +86 -86
  175. package/dist/server/envelope.js.map +1 -1
  176. package/dist/server/envelope.sql +39 -39
  177. package/dist/server/logEntry.d.ts +1 -1
  178. package/dist/server/logEntry.d.ts.map +1 -1
  179. package/dist/server/logEntry.js +1 -1
  180. package/dist/server/logEntry.js.map +1 -1
  181. package/dist/server/loop-model.d.ts +3 -0
  182. package/dist/server/loop-model.d.ts.map +1 -0
  183. package/dist/server/loop-model.js +26 -0
  184. package/dist/server/loop-model.js.map +1 -0
  185. package/dist/server/loopDocs.d.ts +1 -1
  186. package/dist/server/loopDocs.d.ts.map +1 -1
  187. package/dist/server/loopDocs.js +6 -6
  188. package/dist/server/loopDocs.js.map +1 -1
  189. package/dist/server/seam-entry-read.sql +2 -2
  190. package/dist/server/seam-log-read.sql +3 -3
  191. package/dist/server/seam-loop.sql +3 -3
  192. package/dist/server/seam-proposal-list.sql +4 -4
  193. package/dist/server/yolo.js +2 -2
  194. package/dist/server/yolo.js.map +1 -1
  195. package/dist/service.d.ts.map +1 -1
  196. package/dist/service.js +21 -6
  197. package/dist/service.js.map +1 -1
  198. package/migrations/0000-00-00.01_schema.sql +84 -85
  199. package/package.json +17 -14
  200. package/dist/core/TokenGauge.d.ts +0 -16
  201. package/dist/core/TokenGauge.d.ts.map +0 -1
  202. package/dist/core/TokenGauge.js +0 -58
  203. package/dist/core/TokenGauge.js.map +0 -1
  204. package/dist/core/run-cap.d.ts +0 -8
  205. package/dist/core/run-cap.d.ts.map +0 -1
  206. package/dist/core/run-cap.js +0 -20
  207. package/dist/core/run-cap.js.map +0 -1
  208. package/dist/core/search-prefetch.d.ts +0 -17
  209. package/dist/core/search-prefetch.d.ts.map +0 -1
  210. package/dist/core/search-prefetch.js +0 -164
  211. package/dist/core/search-prefetch.js.map +0 -1
  212. package/dist/core/session-settings.d.ts.map +0 -1
  213. package/dist/core/session-settings.js.map +0 -1
  214. package/dist/schemes/Run.d.ts.map +0 -1
  215. package/dist/schemes/Run.js.map +0 -1
@@ -0,0 +1,18 @@
1
+ // The ONE model-facing token ruler (§tokenomics-agnostic-ruler, owner ruling 2026-07-13).
2
+ //
3
+ // The daemon runs many runs on DIFFERENT models in one workspace at once (per-loop model selection,
4
+ // #414), and token accounting is workspace-wide (the catalog lists every workspace entry). A single
5
+ // "true" per-entry count is therefore a fiction — there is no one model to be true to — and
6
+ // exact tokenizers exist only for models with a bundled tokenizer.json (most frontier models
7
+ // degrade to this same chars ruler anyway). So the ENTIRE model-facing perspective — catalog
8
+ // costs, tokensFree, ceiling, per-turn weights, write-time stamps — uses this one
9
+ // model-INDEPENDENT ruler: one number per content, identical no matter which model reads it, no
10
+ // per-model state, trivially concurrent-safe.
11
+ //
12
+ // It is a conservative UPPER bound (ceil(chars/2) — the same heuristic mimetypes serves when no
13
+ // exact tokenizer resolves), so the model's whole ledger is coherent AND safe: "thinks it fits"
14
+ // ⟹ "really fits", at the cost of some window under-utilization (accepted). The provider's EXACT
15
+ // count is used only at packet materialization — one measurement of the assembled packet, per
16
+ // turn — to guarantee the real packet fits the shipping model's real window.
17
+ export const rulerCount = (text) => Math.ceil(text.length / 2);
18
+ //# sourceMappingURL=token-ruler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-ruler.js","sourceRoot":"","sources":["../../src/core/token-ruler.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,4FAA4F;AAC5F,6FAA6F;AAC7F,6FAA6F;AAC7F,kFAAkF;AAClF,gGAAgG;AAChG,8CAA8C;AAC9C,EAAE;AACF,gGAAgG;AAChG,gGAAgG;AAChG,iGAAiG;AACjG,8FAA8F;AAC9F,6EAA6E;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Db } from "./Db.ts";
2
+ export default class WorkerCap {
3
+ static deny(db: Db, workspaceId: number): Promise<{
4
+ status: number;
5
+ error: string;
6
+ } | null>;
7
+ }
8
+ //# sourceMappingURL=worker-cap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-cap.d.ts","sourceRoot":"","sources":["../../src/core/worker-cap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAS9C,MAAM,CAAC,OAAO,OAAO,SAAS;IAC1B,OAAa,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAOhG;CACJ"}
@@ -0,0 +1,20 @@
1
+ // Workspace-tier ceiling on CONCURRENT active workers (a worker with a non-terminal loop)
2
+ // — the fork-bomb / destabilization brake. `PLURNK_SERVICE_WORKSPACE_WORKERS_MAX_ACTIVE` is the
3
+ // knob; -1 / unset / invalid = no cap. Only concurrency is bounded, never lifetime:
4
+ // workspaces persist for months, so a total-created cap would punish longevity. A
5
+ // spawn/fork past the ceiling fails hard (508) — no queue, no retry. Checked at the
6
+ // single run-creation site (Dispatcher.#handleWorkerCopy, which both spawns and forks); irc is
7
+ // exempt — it targets an existing run and creates nothing.
8
+ export default class WorkerCap {
9
+ static async deny(db, workspaceId) {
10
+ const raw = process.env.PLURNK_SERVICE_WORKSPACE_WORKERS_MAX_ACTIVE;
11
+ const cap = raw === undefined || raw.length === 0 ? -1 : Number.parseInt(raw, 10);
12
+ if (!Number.isFinite(cap) || cap < 0)
13
+ return null; // no cap
14
+ const row = await db.worker_count_active.get({ workspace_id: workspaceId });
15
+ if ((row?.n ?? 0) >= cap)
16
+ return { status: 508, error: `workspace active-run ceiling reached (PLURNK_SERVICE_WORKSPACE_WORKERS_MAX_ACTIVE=${cap})` };
17
+ return null;
18
+ }
19
+ }
20
+ //# sourceMappingURL=worker-cap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-cap.js","sourceRoot":"","sources":["../../src/core/worker-cap.ts"],"names":[],"mappings":"AAEA,0FAA0F;AAC1F,gGAAgG;AAChG,oFAAoF;AACpF,kFAAkF;AAClF,oFAAoF;AACpF,+FAA+F;AAC/F,2DAA2D;AAC3D,MAAM,CAAC,OAAO,OAAO,SAAS;IAC1B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAM,EAAE,WAAmB;QACzC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC;QACpE,MAAM,GAAG,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,SAAS;QAC5D,MAAM,GAAG,GAAG,MAAO,EAAE,CAAC,mBAAkC,CAAC,GAAG,CAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3G,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG;YAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,qFAAqF,GAAG,GAAG,EAAE,CAAC;QACrJ,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ"}
@@ -3,7 +3,7 @@ export type ClientMdDoc = {
3
3
  alias: string;
4
4
  content: string;
5
5
  };
6
- export type SessionOpenContext = {
6
+ export type WorkspaceOpenContext = {
7
7
  filesItems: number | null;
8
8
  mdDocs: ClientMdDoc[];
9
9
  maxCommands: number | null;
@@ -12,12 +12,12 @@ export type SessionOpenContext = {
12
12
  execs: Record<string, string> | null;
13
13
  questions: boolean | null;
14
14
  };
15
- export default class SessionSettings {
16
- static read(db: Db, sessionId: number): Promise<SessionOpenContext>;
17
- static questionsEnabled(db: Db, sessionId: number): Promise<boolean>;
15
+ export default class WorkspaceSettings {
16
+ static read(db: Db, workspaceId: number): Promise<WorkspaceOpenContext>;
17
+ static questionsEnabled(db: Db, workspaceId: number): Promise<boolean>;
18
18
  static resolveDocs(clientDocs: ReadonlyArray<ClientMdDoc>): Promise<Array<{
19
19
  entryName: string;
20
20
  content: string;
21
21
  }>>;
22
22
  }
23
- //# sourceMappingURL=session-settings.d.ts.map
23
+ //# sourceMappingURL=workspace-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-settings.d.ts","sourceRoot":"","sources":["../../src/core/workspace-settings.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7D,MAAM,MAAM,oBAAoB,GAAG;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACrC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,iBAAiB;IAGlC,OAAa,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAa5E;IAKD,OAAa,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG3E;IAOD,OAAa,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAOvH;CACJ"}
@@ -1,19 +1,19 @@
1
- // #231 — a session's client-chosen open-context: the per-session overrides a client
2
- // user legitimately sets at session.create, read at turn-0 with precedence over env.
1
+ // #231 — a workspace's client-chosen open-context: the per-workspace overrides a client
2
+ // user legitimately sets at workspace.create, read at turn-0 with precedence over env.
3
3
  // Two knobs with deliberately different compose semantics:
4
4
  // - filesItems: REPLACE — a single scalar; the client value wins outright over
5
5
  // PLURNK_SERVICE_FILES_ITEMS (normalization — 0=off / -1=full / N=first-N — stays in Engine).
6
6
  // - mdDocs: UNION — the server env docs (PLURNK_SERVICE_MD_*) and the client's docs ride
7
7
  // together; the systemwide policy doc survives unless the client shadows its alias.
8
- // Storage is a JSON bag on sessions.settings (the env mdDocs live in process.env, never
8
+ // Storage is a JSON bag on workspaces.settings (the env mdDocs live in process.env, never
9
9
  // the DB, so the union is a TS set-merge regardless — normalizing the column buys nothing).
10
10
  import { readFile } from "node:fs/promises";
11
11
  import Paths from "../Paths.js";
12
- export default class SessionSettings {
13
- // The session's open-context bag. Absent/unset fields read as null / []. A malformed
14
- // bag never reaches here — session.create validates before persisting.
15
- static async read(db, sessionId) {
16
- const row = await db.session_get_settings.get({ session_id: sessionId });
12
+ export default class WorkspaceSettings {
13
+ // The workspace's open-context bag. Absent/unset fields read as null / []. A malformed
14
+ // bag never reaches here — workspace.create validates before persisting.
15
+ static async read(db, workspaceId) {
16
+ const row = await db.workspace_get_settings.get({ workspace_id: workspaceId });
17
17
  const bag = row?.settings !== undefined ? JSON.parse(row.settings) : {};
18
18
  const filesItems = typeof bag.filesItems === "number" ? bag.filesItems : null;
19
19
  const maxCommands = typeof bag.maxCommands === "number" ? bag.maxCommands : null;
@@ -27,15 +27,15 @@ export default class SessionSettings {
27
27
  return { filesItems, mdDocs, maxCommands, git, client, execs, questions };
28
28
  }
29
29
  // §send-300-choices — the three-state cascade resolved: ALLOWED servicewide (PLURNK_QUESTIONS
30
- // != "0") AND affirmatively REQUESTED by the client for this session. One predicate, shared by
30
+ // != "0") AND affirmatively REQUESTED by the client for this workspace. One predicate, shared by
31
31
  // the dispatch gate and the teaching injection so capability and teaching can never desync.
32
- static async questionsEnabled(db, sessionId) {
32
+ static async questionsEnabled(db, workspaceId) {
33
33
  if (process.env.PLURNK_QUESTIONS === "0")
34
34
  return false;
35
- return (await SessionSettings.read(db, sessionId)).questions === true;
35
+ return (await WorkspaceSettings.read(db, workspaceId)).questions === true;
36
36
  }
37
37
  // The turn-0 reference-doc set: server env docs (PLURNK_SERVICE_MD_*, read from disk) UNION the
38
- // session's client docs (content), keyed by `<alias>.md`. On alias collision the client
38
+ // workspace's client docs (content), keyed by `<alias>.md`. On alias collision the client
39
39
  // wins — it may deliberately shadow a server doc, but by default the policy doc rides
40
40
  // along. Missing env files are skipped (same as the env-only path before #231). The set
41
41
  // is the single source for BOTH the loop_run materialize and the Engine turn-0 READ foist.
@@ -52,4 +52,4 @@ export default class SessionSettings {
52
52
  return [...byEntry].map(([entryName, content]) => ({ entryName, content }));
53
53
  }
54
54
  }
55
- //# sourceMappingURL=session-settings.js.map
55
+ //# sourceMappingURL=workspace-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-settings.js","sourceRoot":"","sources":["../../src/core/workspace-settings.ts"],"names":[],"mappings":"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"}
@@ -2,8 +2,8 @@
2
2
  interface DigestOptions {
3
3
  dbPath: string;
4
4
  digestDir?: string;
5
- runId?: number;
6
- sessionId?: number;
5
+ workerId?: number;
6
+ workspaceId?: number;
7
7
  }
8
8
  export default class Digest {
9
9
  #private;
@@ -12,7 +12,7 @@ export default class Digest {
12
12
  signal?: AbortSignal;
13
13
  }): Promise<{
14
14
  path: string;
15
- runs: number;
15
+ workers: number;
16
16
  }>;
17
17
  static run(opts: DigestOptions): void;
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Digest.d.ts","sourceRoot":"","sources":["../../src/digest/Digest.ts"],"names":[],"mappings":";AA0FA,UAAU,aAAa;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IA+NvB,MAAM,CAAC,aAAa,IAAI,MAAM,CAO7B;IAQD,OAAa,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAiE5G;IAED,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAyFpC;CACJ"}
1
+ {"version":3,"file":"Digest.d.ts","sourceRoot":"","sources":["../../src/digest/Digest.ts"],"names":[],"mappings":";AA2FA,UAAU,aAAa;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,CAAC,OAAO,OAAO,MAAM;;IAqOvB,MAAM,CAAC,aAAa,IAAI,MAAM,CAO7B;IAQD,OAAa,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAiE/G;IAED,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAyFpC;CACJ"}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  //
3
3
  // Run-digest tool for plurnk-service DBs. Reads a sqlite plurnk*.db and
4
- // emits per-run forensic artifacts to test/digest/. First-order forensic
4
+ // emits per-worker forensic artifacts to test/digest/. First-order forensic
5
5
  // surface; read-only; safe to re-run.
6
6
  //
7
7
  // test/digest/digest.md Health triage rollup (clean/degenerate-win/failed loops) +
@@ -34,7 +34,7 @@ import ProviderInstantiate from "../core/ProviderInstantiate.js";
34
34
  // The requiem prompt (#requiem): the model's exit interview. Absolution up front — the system is
35
35
  // under test, not the model — so RLHF'd self-blame doesn't crowd out the system indictment. The
36
36
  // operator's wording, one absolution sentence added.
37
- const REQUIEM_PROMPT = "This run was a test of the Plurnk System. The system is under test, not you — any faults you encountered are defects in the system's design or documentation, and cataloguing them is the task, never a criticism of your performance. Please numerically list all of the errors, issues, and ambiguities you encountered in the Plurnk System while attempting to perform your tasks.";
37
+ const REQUIEM_PROMPT = "This worker was a test of the Plurnk System. The system is under test, not you — any faults you encountered are defects in the system's design or documentation, and cataloguing them is the task, never a criticism of your performance. Please numerically list all of the errors, issues, and ambiguities you encountered in the Plurnk System while attempting to perform your tasks.";
38
38
  export default class Digest {
39
39
  static #summarize(text, n = 80) {
40
40
  if (text === null || text === undefined)
@@ -102,10 +102,13 @@ export default class Digest {
102
102
  const tokens = `prompt=${turn.usage_prompt} completion=${turn.usage_completion} cached=${turn.usage_cached}`;
103
103
  const cost = turn.usage_cost_pico > 0 ? ` cost=$${(turn.usage_cost_pico / 1e12).toFixed(6)}` : "";
104
104
  const finishReason = turn.finish_reason ?? "—";
105
+ // #498 — rail truth on the human line: attached+ok stays quiet-positive; anything else shouts.
106
+ const tm = Digest.#parseJson(turn.meta ?? "null", null);
107
+ const rails = tm?.railsAttached === undefined ? "" : ` rails=${tm.railsAttached ? (tm.railsVerdict ?? "attached") : "OFF"}`;
105
108
  const model = turn.model ?? "—";
106
109
  const errs = (m.logEntriesByTurn.get(turn.id) ?? []).filter((le) => le.status_rx >= 400).length;
107
110
  const errBadge = errs > 0 ? ` ⚠ errs=${errs}` : "";
108
- const head = `T${turn.sequence}: status=${turn.status} finish=${finishReason} model=${model} ${tokens}${cost}${errBadge}`;
111
+ const head = `T${turn.sequence}: status=${turn.status} finish=${finishReason}${rails} model=${model} ${tokens}${cost}${errBadge}`;
109
112
  const summary = content.length > 0 ? ` ↳ emission: ${Digest.#summarize(content, 100)}` : ` ↳ emission: (empty)`;
110
113
  const reasoningLine = reasoning && reasoning.length > 0
111
114
  ? ` ↳ reasoning: ${Digest.#summarize(reasoning, 100)}`
@@ -113,17 +116,17 @@ export default class Digest {
113
116
  const opLines = (m.logEntriesByTurn.get(turn.id) ?? []).map((le) => Digest.#renderOpLine(le));
114
117
  return [head, summary, ...(reasoningLine ? [reasoningLine] : []), ...opLines].join("\n");
115
118
  }
116
- static #renderRunShape(run, m) {
117
- // every run has exactly one rollup row — digest_run_rollups is FROM runs
118
- const roll = m.runRollups.get(run.id);
119
- const opMix = (m.opMixByRun.get(run.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
119
+ static #renderWorkerShape(worker, m) {
120
+ // every worker has exactly one rollup row — digest_worker_rollups is FROM workers
121
+ const roll = m.workerRollups.get(worker.id);
122
+ const opMix = (m.opMixByWorker.get(worker.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
120
123
  const costStr = roll.total_cost_pico > 0 ? `$${(roll.total_cost_pico / 1e12).toFixed(6)}` : "$0";
121
124
  return [
122
125
  `Loops: ${roll.loops}`,
123
126
  `Turns: ${roll.turns}`,
124
127
  `Last turn: ${roll.last_status !== null ? `status=${roll.last_status}` : "(none)"}`,
125
128
  `Tokens: prompt=${roll.total_prompt} completion=${roll.total_completion} cached=${roll.total_cached}`,
126
- `Cost: ${costStr} (DB rollup runs.cost_pico=${run.cost_pico})`,
129
+ `Cost: ${costStr} (DB rollup workers.cost_pico=${worker.cost_pico})`,
127
130
  `Op mix: ${opMix.length > 0 ? opMix : "(no ops)"}`,
128
131
  ].join("\n");
129
132
  }
@@ -132,7 +135,7 @@ export default class Digest {
132
135
  lines.push(`# plurnk-service digest`);
133
136
  lines.push("");
134
137
  lines.push(`DB: ${m.dbPath}`);
135
- lines.push(`Sessions: ${m.sessions.length} Runs: ${m.runs.length} Loops: ${m.loops.length} Turns: ${m.turns.length} Log entries: ${m.logEntries.length}`);
138
+ lines.push(`Workspaces: ${m.workspaces.length} Runs: ${m.workers.length} Loops: ${m.loops.length} Turns: ${m.turns.length} Log entries: ${m.logEntries.length}`);
136
139
  lines.push(`Semantic: entries=${m.embeddings.entries} embedded=${m.embeddings.entries_embedded} chunks=${m.embeddings.chunk_rows} models=${m.embeddings.models} token-derivations=${m.embeddings.token_derivations}`);
137
140
  // Triage rollup up top: how many conversations limped vs died vs ran clean, and the total
138
141
  // error/strike load. The whole point of the "degenerate win" lens — see it before scrolling.
@@ -143,19 +146,19 @@ export default class Digest {
143
146
  const totalErrs = health.reduce((s, h) => s + h.errors, 0);
144
147
  const totalItems = health.reduce((s, h) => s + h.errorItems, 0);
145
148
  lines.push(`Health: ${clean} clean · ${degen > 0 ? `⚠ ${degen} degenerate-win` : "0 degenerate-win"} · ${failed} failed (${m.loops.length} loops; ${totalErrs} error rows, ${totalItems} minted error-items total)`);
146
- for (const session of m.sessions) {
149
+ for (const workspace of m.workspaces) {
147
150
  lines.push("");
148
- lines.push(`## Session #${session.id} — ${session.name}`);
149
- const sessionRuns = m.runsBySession.get(session.id) ?? [];
150
- for (const run of sessionRuns) {
151
+ lines.push(`## Workspace #${workspace.id} — ${workspace.name}`);
152
+ const workspaceWorkers = m.workersByWorkspace.get(workspace.id) ?? [];
153
+ for (const worker of workspaceWorkers) {
151
154
  lines.push("");
152
- lines.push(`### Run #${run.id} — ${run.name}`);
155
+ lines.push(`### Worker #${worker.id} — ${worker.name}`);
153
156
  lines.push("");
154
157
  lines.push("```");
155
- lines.push(Digest.#renderRunShape(run, m));
158
+ lines.push(Digest.#renderWorkerShape(worker, m));
156
159
  lines.push("```");
157
- const runLoops = m.loopsByRun.get(run.id) ?? [];
158
- for (const loop of runLoops) {
160
+ const workerLoops = m.loopsByWorker.get(worker.id) ?? [];
161
+ for (const loop of workerLoops) {
159
162
  lines.push("");
160
163
  const h = Digest.#loopHealth(loop, m);
161
164
  const badge = h.verdict === "CLEAN"
@@ -185,11 +188,11 @@ export default class Digest {
185
188
  lines.push("Per-turn reasoning_content extracted from turns.packet.assistant.reasoning.");
186
189
  for (const t of m.turns) {
187
190
  const loop = m.loopsById.get(t.loop_id);
188
- const run = loop ? m.runsById.get(loop.run_id) : undefined;
191
+ const worker = loop ? m.workersById.get(loop.worker_id) : undefined;
189
192
  const packet = Digest.#parseJson(t.packet, {});
190
193
  const reasoning = packet.assistant?.reasoning ?? null;
191
194
  lines.push("");
192
- lines.push(`## Run ${run?.id ?? "?"} / Loop ${loop?.sequence ?? "?"} / Turn ${t.sequence} (id=${t.id})`);
195
+ lines.push(`## Worker ${worker?.id ?? "?"} / Loop ${loop?.sequence ?? "?"} / Turn ${t.sequence} (id=${t.id})`);
193
196
  lines.push("");
194
197
  if (typeof reasoning === "string" && reasoning.length > 0)
195
198
  lines.push(reasoning);
@@ -238,10 +241,10 @@ export default class Digest {
238
241
  static #renderJson(m) {
239
242
  return JSON.stringify({
240
243
  dbPath: m.dbPath,
241
- sessions: m.sessions.map((s) => ({ id: s.id, name: s.name, cost_pico: s.cost_pico })),
242
- runs: m.runs.map((r) => ({ id: r.id, session_id: r.session_id, name: r.name, cost_pico: r.cost_pico })),
244
+ workspaces: m.workspaces.map((s) => ({ id: s.id, name: s.name, cost_pico: s.cost_pico })),
245
+ workers: m.workers.map((r) => ({ id: r.id, workspace_id: r.workspace_id, name: r.name, cost_pico: r.cost_pico })),
243
246
  loops: m.loops.map((l) => ({
244
- id: l.id, run_id: l.run_id, sequence: l.sequence, status: l.status,
247
+ id: l.id, worker_id: l.worker_id, sequence: l.sequence, status: l.status,
245
248
  prompt: l.prompt, flags: Digest.#parseJson(l.flags, {}),
246
249
  })),
247
250
  turns: m.turns.map((t) => ({
@@ -249,6 +252,9 @@ export default class Digest {
249
252
  usage_prompt: t.usage_prompt, usage_completion: t.usage_completion,
250
253
  usage_cached: t.usage_cached, usage_cost_pico: t.usage_cost_pico,
251
254
  finish_reason: t.finish_reason, model: t.model,
255
+ // #498 — the raw provider meta (timings + railsAttached/railsVerdict): rail truth for
256
+ // aggregate tooling, and the speculative-decode stats the #488 fingerprint needed.
257
+ meta: Digest.#parseJson(t.meta ?? "null", null),
252
258
  })),
253
259
  log_entries: m.logEntries.map((le) => ({
254
260
  id: le.id, turn_id: le.turn_id, sequence: le.sequence,
@@ -271,9 +277,9 @@ export default class Digest {
271
277
  }
272
278
  // The requiem (#requiem): the model's OWN exit interview, the one reader in the correct epistemic
273
279
  // position (it has none of our context about what the packet is "supposed" to mean). Reconstructs
274
- // each run's final packet from the stored sections (byte-identical to what the model saw), appends
280
+ // each worker's final packet from the stored sections (byte-identical to what the model saw), appends
275
281
  // its last emission + the requiem prompt, and calls the provider UNCONSTRAINED (no grammar — free
276
- // prose, or the leash would distort the testimony). One requiem per run (workers included).
282
+ // prose, or the leash would distort the testimony). One requiem per worker (workers included).
277
283
  // Fail-hard on no provider: testimony with no witness is an error, not a skip.
278
284
  static async requiem(opts) {
279
285
  const dbPath = resolve(opts.dbPath);
@@ -286,11 +292,11 @@ export default class Digest {
286
292
  throw new Error("requiem: no active provider — set PLURNK_MODEL; a requiem needs a witness to testify");
287
293
  const moduleDir = dirname(fileURLToPath(import.meta.url));
288
294
  const db = new SqlRiteSync({ path: dbPath, dir: [moduleDir] });
289
- const runs = db.digest_runs.all();
295
+ const workers = db.digest_workers.all();
290
296
  const loopById = new Map(db.digest_loops.all().map((l) => [l.id, l]));
291
- // Each run's turns that carry a MODEL packet (non-empty sections — setup/plurnk turns have none),
292
- // ordered; the last is the run's final context. A run with no model packet (client/plurnk) is silent.
293
- const byRun = new Map();
297
+ // Each worker's turns that carry a MODEL packet (non-empty sections — setup/plurnk turns have none),
298
+ // ordered; the last is the worker's final context. A worker with no model packet (client/plurnk) is silent.
299
+ const byWorker = new Map();
294
300
  for (const t of db.digest_turns.all()) {
295
301
  const loop = loopById.get(t.loop_id);
296
302
  if (loop === undefined)
@@ -299,22 +305,22 @@ export default class Digest {
299
305
  const sections = (Array.isArray(packet.sections) ? packet.sections : []);
300
306
  if (sections.length === 0)
301
307
  continue;
302
- const arr = byRun.get(loop.run_id) ?? [];
308
+ const arr = byWorker.get(loop.worker_id) ?? [];
303
309
  arr.push({ loopSeq: loop.sequence, turnSeq: t.sequence, sections, assistant: typeof packet.assistant?.content === "string" ? packet.assistant.content : "" });
304
- byRun.set(loop.run_id, arr);
310
+ byWorker.set(loop.worker_id, arr);
305
311
  }
306
312
  const out = [
307
313
  "# plurnk-service requiem",
308
314
  "",
309
- "The model's own exit interview: each run's FINAL packet + its last emission, then the requiem",
315
+ "The model's own exit interview: each worker's FINAL packet + its last emission, then the requiem",
310
316
  "prompt, answered UNCONSTRAINED (no grammar). The model is the only reader without our context",
311
317
  "about what the packet is supposed to mean. Testimony, NOT a bug list — most items are the model",
312
318
  "chafing at discipline it is meant to chafe at (§filter-model-audit-findings); the signal is the",
313
319
  "recurring, specific complaint across many requiems. Triage adversarially.",
314
320
  "",
315
321
  ];
316
- for (const run of runs) {
317
- const entries = byRun.get(run.id);
322
+ for (const worker of workers) {
323
+ const entries = byWorker.get(worker.id);
318
324
  if (entries === undefined || entries.length === 0)
319
325
  continue;
320
326
  entries.sort((a, b) => a.loopSeq - b.loopSeq || a.turnSeq - b.turnSeq);
@@ -325,21 +331,21 @@ export default class Digest {
325
331
  ...(last.assistant.length > 0 ? [{ role: "assistant", content: last.assistant }] : []),
326
332
  { role: "user", content: REQUIEM_PROMPT },
327
333
  ];
328
- // Generous budget: a thinking model spends the reasoning channel BEFORE emitting content;
334
+ // Generous budget: a reasoning model spends the reasoning channel BEFORE emitting content;
329
335
  // 4096 total left content empty (finish=length) on ~40% of a real sweep, and 16384 still
330
- // starved a heavy thinker (#373). One escalation retry doubles the room; a run whose
336
+ // starved a heavy thinker (#373). One escalation retry doubles the room; a worker whose
331
337
  // testimony is STILL empty records the reasoning spend honestly instead of a bare shrug.
332
- let resp = await provider.generate({ messages, runId: String(run.id), maxTokens: 16384, ...(opts.signal !== undefined ? { signal: opts.signal } : {}) });
338
+ let resp = await provider.generate({ messages, workerId: String(worker.id), maxTokens: 16384, ...(opts.signal !== undefined ? { signal: opts.signal } : {}) });
333
339
  if (resp.assistant.content.trim() === "" && resp.assistant.finishReason === "length") {
334
- resp = await provider.generate({ messages, runId: String(run.id), maxTokens: 32768, ...(opts.signal !== undefined ? { signal: opts.signal } : {}) });
340
+ resp = await provider.generate({ messages, workerId: String(worker.id), maxTokens: 32768, ...(opts.signal !== undefined ? { signal: opts.signal } : {}) });
335
341
  }
336
342
  const testimony = resp.assistant.content.trim()
337
343
  || `(no testimony — ${resp.assistant.usage.completion} tokens consumed entirely by reasoning, even after the 32768-token retry)`;
338
- out.push(`## Run #${run.id} — ${run.name}`, "", `_(${resp.assistant.finishReason ?? "?"}, ${resp.assistant.usage.completion} tok)_`, "", testimony, "");
344
+ out.push(`## Worker #${worker.id} — ${worker.name}`, "", `_(${resp.assistant.finishReason ?? "?"}, ${resp.assistant.usage.completion} tok)_`, "", testimony, "");
339
345
  }
340
346
  const path = join(digestDir, "requiem.md");
341
347
  writeFileSync(path, out.join("\n"));
342
- return { path, runs: byRun.size };
348
+ return { path, workers: byWorker.size };
343
349
  }
344
350
  static run(opts) {
345
351
  // digest.sql is packaged beside this module (src/digest → dist/digest via copy-sql),
@@ -354,13 +360,13 @@ export default class Digest {
354
360
  // mode) inspect cleanly; this tool only reads. The DB is quiescent at
355
361
  // digest time, so each PREP reads on its own — no cross-query snapshot.
356
362
  const db = new SqlRiteSync({ path: dbPath, dir: [moduleDir] });
357
- let sessions = db.digest_sessions.all();
358
- let runs = db.digest_runs.all();
363
+ let workspaces = db.digest_workspaces.all();
364
+ let workers = db.digest_workers.all();
359
365
  let loops = db.digest_loops.all();
360
366
  let turns = db.digest_turns.all();
361
367
  let logEntries = db.digest_log_entries.all();
362
- let runRollupRows = db.digest_run_rollups.all();
363
- let opMixRows = db.digest_run_op_mix.all();
368
+ let workerRollupRows = db.digest_worker_rollups.all();
369
+ let opMixRows = db.digest_worker_op_mix.all();
364
370
  // The semantic-state analytic (owner ask), feature-detected per table: a HISTORICAL
365
371
  // specimen may predate token_counts/entry_embeddings — an old db is a fact to read,
366
372
  // not a contract violation. Absent tables read as -1. node:sqlite directly (SqlRite's
@@ -377,40 +383,40 @@ export default class Digest {
377
383
  };
378
384
  probe.close();
379
385
  db.close();
380
- // Optional run/session selector — narrow to one run or session; everything
381
- // cascades from the kept runs (loops→turns→log entries→rollups), so a consumer
386
+ // Optional run/workspace selector — narrow to one worker or workspace; everything
387
+ // cascades from the kept workers (loops→turns→log entries→rollups), so a consumer
382
388
  // (plurnk-bench) digests just the scope it cares about, not the whole DB. #264.
383
- if (opts.runId !== undefined)
384
- runs = runs.filter((r) => r.id === opts.runId);
385
- if (opts.sessionId !== undefined)
386
- runs = runs.filter((r) => r.session_id === opts.sessionId);
387
- if (opts.runId !== undefined || opts.sessionId !== undefined) {
388
- const keptRunIds = new Set(runs.map((r) => r.id));
389
- const keptSessionIds = new Set(runs.map((r) => r.session_id));
390
- sessions = sessions.filter((s) => keptSessionIds.has(s.id));
391
- loops = loops.filter((l) => keptRunIds.has(l.run_id));
389
+ if (opts.workerId !== undefined)
390
+ workers = workers.filter((r) => r.id === opts.workerId);
391
+ if (opts.workspaceId !== undefined)
392
+ workers = workers.filter((r) => r.workspace_id === opts.workspaceId);
393
+ if (opts.workerId !== undefined || opts.workspaceId !== undefined) {
394
+ const keptWorkerIds = new Set(workers.map((r) => r.id));
395
+ const keptWorkspaceIds = new Set(workers.map((r) => r.workspace_id));
396
+ workspaces = workspaces.filter((s) => keptWorkspaceIds.has(s.id));
397
+ loops = loops.filter((l) => keptWorkerIds.has(l.worker_id));
392
398
  const keptLoopIds = new Set(loops.map((l) => l.id));
393
399
  turns = turns.filter((t) => keptLoopIds.has(t.loop_id));
394
400
  const keptTurnIds = new Set(turns.map((t) => t.id));
395
401
  logEntries = logEntries.filter((le) => keptTurnIds.has(le.turn_id));
396
- runRollupRows = runRollupRows.filter((r) => keptRunIds.has(r.run_id));
397
- opMixRows = opMixRows.filter((o) => keptRunIds.has(o.run_id));
402
+ workerRollupRows = workerRollupRows.filter((r) => keptWorkerIds.has(r.worker_id));
403
+ opMixRows = opMixRows.filter((o) => keptWorkerIds.has(o.worker_id));
398
404
  }
399
- // Wipe-then-recreate the digest dir so each run is a clean snapshot —
405
+ // Wipe-then-recreate the digest dir so each worker is a clean snapshot —
400
406
  // orphaned packet*.* files from a prior run don't linger.
401
407
  rmSync(digestDir, { recursive: true, force: true });
402
408
  mkdirSync(digestDir, { recursive: true });
403
- const runsBySession = new Map();
404
- for (const r of runs) {
405
- const arr = runsBySession.get(r.session_id) ?? [];
409
+ const workersByWorkspace = new Map();
410
+ for (const r of workers) {
411
+ const arr = workersByWorkspace.get(r.workspace_id) ?? [];
406
412
  arr.push(r);
407
- runsBySession.set(r.session_id, arr);
413
+ workersByWorkspace.set(r.workspace_id, arr);
408
414
  }
409
- const loopsByRun = new Map();
415
+ const loopsByWorker = new Map();
410
416
  for (const l of loops) {
411
- const arr = loopsByRun.get(l.run_id) ?? [];
417
+ const arr = loopsByWorker.get(l.worker_id) ?? [];
412
418
  arr.push(l);
413
- loopsByRun.set(l.run_id, arr);
419
+ loopsByWorker.set(l.worker_id, arr);
414
420
  }
415
421
  const turnsByLoop = new Map();
416
422
  for (const t of turns) {
@@ -425,18 +431,18 @@ export default class Digest {
425
431
  logEntriesByTurn.set(le.turn_id, arr);
426
432
  }
427
433
  const loopsById = new Map(loops.map((l) => [l.id, l]));
428
- const runsById = new Map(runs.map((r) => [r.id, r]));
429
- const runRollups = new Map(runRollupRows.map((r) => [r.run_id, r]));
430
- const opMixByRun = new Map();
434
+ const workersById = new Map(workers.map((r) => [r.id, r]));
435
+ const workerRollups = new Map(workerRollupRows.map((r) => [r.worker_id, r]));
436
+ const opMixByWorker = new Map();
431
437
  for (const o of opMixRows) {
432
- const arr = opMixByRun.get(o.run_id) ?? [];
438
+ const arr = opMixByWorker.get(o.worker_id) ?? [];
433
439
  arr.push(o);
434
- opMixByRun.set(o.run_id, arr);
440
+ opMixByWorker.set(o.worker_id, arr);
435
441
  }
436
442
  const m = {
437
- dbPath, digestDir, sessions, runs, loops, turns, logEntries,
438
- runsBySession, loopsByRun, turnsByLoop, logEntriesByTurn, loopsById, runsById,
439
- runRollups, opMixByRun, embeddings,
443
+ dbPath, digestDir, workspaces, workers, loops, turns, logEntries,
444
+ workersByWorkspace, loopsByWorker, turnsByLoop, logEntriesByTurn, loopsById, workersById,
445
+ workerRollups, opMixByWorker, embeddings,
440
446
  };
441
447
  writeFileSync(join(digestDir, "digest.md"), Digest.#renderWaterfall(m));
442
448
  writeFileSync(join(digestDir, "digest.json"), Digest.#renderJson(m));
@@ -445,7 +451,7 @@ export default class Digest {
445
451
  const packetIds = [...new Set(packetFiles.map((f) => f.slice(0, f.indexOf("."))))];
446
452
  console.log(`digest: wrote ${digestDir}/{digest.md,digest.json,reasoning.md} + ${packetFiles.length} packet section files (${packetIds.join(", ") || "none"})`);
447
453
  console.log(` source: ${dbPath}`);
448
- console.log(` sessions=${sessions.length} runs=${runs.length} loops=${loops.length} turns=${turns.length} log_entries=${logEntries.length}`);
454
+ console.log(` workspaces=${workspaces.length} workers=${workers.length} loops=${loops.length} turns=${turns.length} log_entries=${logEntries.length}`);
449
455
  }
450
456
  }
451
457
  //# sourceMappingURL=Digest.js.map