@agimon-ai/doompi-core 0.0.1-alpha.71 → 0.0.1-alpha.72

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 (205) hide show
  1. package/dist/constants/contextApi.cjs +1 -1
  2. package/dist/constants/contextApi.cjs.map +1 -1
  3. package/dist/constants/contextApi.d.cts +1 -1
  4. package/dist/constants/contextApi.d.mts +1 -1
  5. package/dist/constants/contextApi.mjs +1 -1
  6. package/dist/constants/contextApi.mjs.map +1 -1
  7. package/dist/constants/remote.cjs.map +1 -1
  8. package/dist/constants/remote.mjs.map +1 -1
  9. package/dist/context-api.d.cts +2 -2
  10. package/dist/context-api.d.mts +2 -2
  11. package/dist/context-detail.cjs +7 -0
  12. package/dist/context-detail.cjs.map +1 -1
  13. package/dist/context-detail.d.cts +7 -1
  14. package/dist/context-detail.d.cts.map +1 -1
  15. package/dist/context-detail.d.mts +7 -1
  16. package/dist/context-detail.d.mts.map +1 -1
  17. package/dist/context-detail.mjs +7 -0
  18. package/dist/context-detail.mjs.map +1 -1
  19. package/dist/context-projection.cjs +1 -0
  20. package/dist/context-projection.cjs.map +1 -1
  21. package/dist/context-projection.d.cts +15 -0
  22. package/dist/context-projection.d.cts.map +1 -1
  23. package/dist/context-projection.d.mts +15 -0
  24. package/dist/context-projection.d.mts.map +1 -1
  25. package/dist/context-projection.mjs +1 -0
  26. package/dist/context-projection.mjs.map +1 -1
  27. package/dist/headless-hub.cjs +60 -16
  28. package/dist/headless-hub.cjs.map +1 -1
  29. package/dist/headless-hub.d.cts +5 -2
  30. package/dist/headless-hub.d.cts.map +1 -1
  31. package/dist/headless-hub.d.mts +5 -2
  32. package/dist/headless-hub.d.mts.map +1 -1
  33. package/dist/headless-hub.mjs +60 -16
  34. package/dist/headless-hub.mjs.map +1 -1
  35. package/dist/headless-server.cjs +138 -41
  36. package/dist/headless-server.cjs.map +1 -1
  37. package/dist/headless-server.d.cts +4 -0
  38. package/dist/headless-server.d.cts.map +1 -1
  39. package/dist/headless-server.d.mts +4 -0
  40. package/dist/headless-server.d.mts.map +1 -1
  41. package/dist/headless-server.mjs +139 -42
  42. package/dist/headless-server.mjs.map +1 -1
  43. package/dist/headless-session-host.d.cts +5 -0
  44. package/dist/headless-session-host.d.cts.map +1 -1
  45. package/dist/headless-session-host.d.mts +5 -0
  46. package/dist/headless-session-host.d.mts.map +1 -1
  47. package/dist/headless.d.cts +2 -2
  48. package/dist/headless.d.mts +2 -2
  49. package/dist/history.cjs +2 -0
  50. package/dist/history.d.cts +2 -1
  51. package/dist/history.d.mts +2 -1
  52. package/dist/history.mjs +2 -1
  53. package/dist/package-api-server.cjs +3 -2
  54. package/dist/package-api-server.cjs.map +1 -1
  55. package/dist/package-api-server.d.cts +4 -0
  56. package/dist/package-api-server.d.cts.map +1 -1
  57. package/dist/package-api-server.d.mts +4 -0
  58. package/dist/package-api-server.d.mts.map +1 -1
  59. package/dist/package-api-server.mjs +3 -2
  60. package/dist/package-api-server.mjs.map +1 -1
  61. package/dist/package-api.cjs +1 -0
  62. package/dist/package-api.d.cts +2 -2
  63. package/dist/package-api.d.mts +2 -2
  64. package/dist/package-api.mjs +2 -2
  65. package/dist/remote-runtime.cjs +3 -2
  66. package/dist/remote-runtime.cjs.map +1 -1
  67. package/dist/remote-runtime.d.cts +1 -1
  68. package/dist/remote-runtime.d.cts.map +1 -1
  69. package/dist/remote-runtime.d.mts +1 -1
  70. package/dist/remote-runtime.d.mts.map +1 -1
  71. package/dist/remote-runtime.mjs +3 -2
  72. package/dist/remote-runtime.mjs.map +1 -1
  73. package/dist/runtime-context-api.cjs +2 -2
  74. package/dist/runtime-context-api.cjs.map +1 -1
  75. package/dist/runtime-context-api.mjs +2 -2
  76. package/dist/runtime-context-api.mjs.map +1 -1
  77. package/dist/schemas/headless.cjs.map +1 -1
  78. package/dist/schemas/headless.d.cts +10 -0
  79. package/dist/schemas/headless.d.cts.map +1 -1
  80. package/dist/schemas/headless.d.mts +10 -0
  81. package/dist/schemas/headless.d.mts.map +1 -1
  82. package/dist/schemas/headless.mjs.map +1 -1
  83. package/dist/schemas/httpApiContracts.cjs +56 -41
  84. package/dist/schemas/httpApiContracts.cjs.map +1 -1
  85. package/dist/schemas/httpApiContracts.mjs +56 -41
  86. package/dist/schemas/httpApiContracts.mjs.map +1 -1
  87. package/dist/schemas/packageApi.cjs +25 -4
  88. package/dist/schemas/packageApi.cjs.map +1 -1
  89. package/dist/schemas/packageApi.d.cts +18 -3
  90. package/dist/schemas/packageApi.d.cts.map +1 -1
  91. package/dist/schemas/packageApi.d.mts +18 -3
  92. package/dist/schemas/packageApi.d.mts.map +1 -1
  93. package/dist/schemas/packageApi.mjs +25 -5
  94. package/dist/schemas/packageApi.mjs.map +1 -1
  95. package/dist/server/directHarnessRuntime.cjs +15 -1
  96. package/dist/server/directHarnessRuntime.cjs.map +1 -1
  97. package/dist/server/directHarnessRuntime.mjs +15 -1
  98. package/dist/server/directHarnessRuntime.mjs.map +1 -1
  99. package/dist/server/headlessProtocol.cjs +60 -10
  100. package/dist/server/headlessProtocol.cjs.map +1 -1
  101. package/dist/server/headlessProtocol.mjs +60 -10
  102. package/dist/server/headlessProtocol.mjs.map +1 -1
  103. package/dist/services/apiContracts/index.cjs +48 -37
  104. package/dist/services/apiContracts/index.cjs.map +1 -1
  105. package/dist/services/apiContracts/index.mjs +48 -37
  106. package/dist/services/apiContracts/index.mjs.map +1 -1
  107. package/dist/services/contextApiUrls/index.cjs +2 -3
  108. package/dist/services/contextApiUrls/index.cjs.map +1 -1
  109. package/dist/services/contextApiUrls/index.d.cts +1 -1
  110. package/dist/services/contextApiUrls/index.d.cts.map +1 -1
  111. package/dist/services/contextApiUrls/index.d.mts +1 -1
  112. package/dist/services/contextApiUrls/index.d.mts.map +1 -1
  113. package/dist/services/contextApiUrls/index.mjs +3 -4
  114. package/dist/services/contextApiUrls/index.mjs.map +1 -1
  115. package/dist/services/openSessionRegistry/index.cjs +115 -0
  116. package/dist/services/openSessionRegistry/index.cjs.map +1 -0
  117. package/dist/services/openSessionRegistry/index.d.cts +31 -0
  118. package/dist/services/openSessionRegistry/index.d.cts.map +1 -0
  119. package/dist/services/openSessionRegistry/index.d.mts +31 -0
  120. package/dist/services/openSessionRegistry/index.d.mts.map +1 -0
  121. package/dist/services/openSessionRegistry/index.mjs +112 -0
  122. package/dist/services/openSessionRegistry/index.mjs.map +1 -0
  123. package/dist/services/packageApiHarness/index.cjs.map +1 -1
  124. package/dist/services/packageApiHarness/index.d.cts +2 -2
  125. package/dist/services/packageApiHarness/index.d.mts +2 -2
  126. package/dist/services/packageApiHarness/index.mjs.map +1 -1
  127. package/dist/services/piExtensionHost/index.cjs +370 -0
  128. package/dist/services/piExtensionHost/index.cjs.map +1 -0
  129. package/dist/services/piExtensionHost/index.mjs +367 -0
  130. package/dist/services/piExtensionHost/index.mjs.map +1 -0
  131. package/dist/services/piSessionEntries/index.cjs +92 -0
  132. package/dist/services/piSessionEntries/index.cjs.map +1 -0
  133. package/dist/services/piSessionEntries/index.mjs +89 -0
  134. package/dist/services/piSessionEntries/index.mjs.map +1 -0
  135. package/dist/services/sessionPresentation/index.cjs +2 -0
  136. package/dist/services/sessionPresentation/index.cjs.map +1 -1
  137. package/dist/services/sessionPresentation/index.mjs +2 -0
  138. package/dist/services/sessionPresentation/index.mjs.map +1 -1
  139. package/dist/services/toolPrompt/index.cjs +13 -0
  140. package/dist/services/toolPrompt/index.cjs.map +1 -0
  141. package/dist/services/toolPrompt/index.mjs +13 -0
  142. package/dist/services/toolPrompt/index.mjs.map +1 -0
  143. package/dist/services/webauthnPolicy/index.cjs +9 -29
  144. package/dist/services/webauthnPolicy/index.cjs.map +1 -1
  145. package/dist/services/webauthnPolicy/index.d.cts.map +1 -1
  146. package/dist/services/webauthnPolicy/index.d.mts.map +1 -1
  147. package/dist/services/webauthnPolicy/index.mjs +9 -29
  148. package/dist/services/webauthnPolicy/index.mjs.map +1 -1
  149. package/dist/session-files-api.cjs +35 -6
  150. package/dist/session-files-api.cjs.map +1 -1
  151. package/dist/session-files-api.d.cts.map +1 -1
  152. package/dist/session-files-api.d.mts.map +1 -1
  153. package/dist/session-files-api.mjs +34 -6
  154. package/dist/session-files-api.mjs.map +1 -1
  155. package/dist/systems/child/adapters/headlessChildSessionService.cjs +29 -7
  156. package/dist/systems/child/adapters/headlessChildSessionService.cjs.map +1 -1
  157. package/dist/systems/child/adapters/headlessChildSessionService.d.cts +7 -1
  158. package/dist/systems/child/adapters/headlessChildSessionService.d.cts.map +1 -1
  159. package/dist/systems/child/adapters/headlessChildSessionService.d.mts +7 -1
  160. package/dist/systems/child/adapters/headlessChildSessionService.d.mts.map +1 -1
  161. package/dist/systems/child/adapters/headlessChildSessionService.mjs +28 -8
  162. package/dist/systems/child/adapters/headlessChildSessionService.mjs.map +1 -1
  163. package/dist/systems/main/adapters/headlessHost.cjs +22 -4
  164. package/dist/systems/main/adapters/headlessHost.cjs.map +1 -1
  165. package/dist/systems/main/adapters/headlessHost.d.cts +11 -0
  166. package/dist/systems/main/adapters/headlessHost.d.cts.map +1 -1
  167. package/dist/systems/main/adapters/headlessHost.d.mts +11 -0
  168. package/dist/systems/main/adapters/headlessHost.d.mts.map +1 -1
  169. package/dist/systems/main/adapters/headlessHost.mjs +22 -4
  170. package/dist/systems/main/adapters/headlessHost.mjs.map +1 -1
  171. package/dist/systems/main/adapters/headlessSessionHost.cjs +162 -24
  172. package/dist/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
  173. package/dist/systems/main/adapters/headlessSessionHost.d.cts.map +1 -1
  174. package/dist/systems/main/adapters/headlessSessionHost.d.mts.map +1 -1
  175. package/dist/systems/main/adapters/headlessSessionHost.mjs +162 -24
  176. package/dist/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
  177. package/dist/terminal-pi-child-session-service.cjs +7 -13
  178. package/dist/terminal-pi-child-session-service.cjs.map +1 -1
  179. package/dist/terminal-pi-child-session-service.d.cts +6 -1
  180. package/dist/terminal-pi-child-session-service.d.cts.map +1 -1
  181. package/dist/terminal-pi-child-session-service.d.mts +6 -1
  182. package/dist/terminal-pi-child-session-service.d.mts.map +1 -1
  183. package/dist/terminal-pi-child-session-service.mjs +8 -14
  184. package/dist/terminal-pi-child-session-service.mjs.map +1 -1
  185. package/dist/types/server/directHarnessRuntime.d.cts +2 -1
  186. package/dist/types/server/directHarnessRuntime.d.cts.map +1 -1
  187. package/dist/types/server/directHarnessRuntime.d.mts +2 -1
  188. package/dist/types/server/directHarnessRuntime.d.mts.map +1 -1
  189. package/dist/types-context-api.d.cts +25 -2
  190. package/dist/types-context-api.d.cts.map +1 -1
  191. package/dist/types-context-api.d.mts +25 -2
  192. package/dist/types-context-api.d.mts.map +1 -1
  193. package/dist/web/services/apiRoutes/index.cjs +17 -0
  194. package/dist/web/services/apiRoutes/index.cjs.map +1 -0
  195. package/dist/web/services/apiRoutes/index.d.cts +7 -0
  196. package/dist/web/services/apiRoutes/index.d.cts.map +1 -0
  197. package/dist/web/services/apiRoutes/index.d.mts +7 -0
  198. package/dist/web/services/apiRoutes/index.d.mts.map +1 -0
  199. package/dist/web/services/apiRoutes/index.mjs +16 -0
  200. package/dist/web/services/apiRoutes/index.mjs.map +1 -0
  201. package/dist/web.cjs +15 -12
  202. package/dist/web.d.cts +2 -1
  203. package/dist/web.d.mts +2 -1
  204. package/dist/web.mjs +2 -1
  205. package/package.json +16 -15
@@ -9,7 +9,7 @@
9
9
  * is the contract both ends of that request share, and it is deliberately free
10
10
  * of Node imports so a browser can hold it too.
11
11
  */
12
- /** Where a host mounts this package's API; the segment after /api/plugin/. */
12
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
13
13
  const API_BASE_PATH = "context";
14
14
  /** The session a proxied request belongs to; the hub strips it before forwarding. */
15
15
  const SESSION_QUERY_PARAM = "session";
@@ -1 +1 @@
1
- {"version":3,"file":"contextApi.cjs","names":[],"sources":["../../src/constants/contextApi.ts"],"sourcesContent":["/**\n * What one entry in the composition actually is, for a reader who clicked it.\n *\n * The projection prices the composition and stops there: names, owners, and\n * integer counts, small enough to republish whenever the composition changes.\n * The prose and the schema behind a figure are an order of magnitude larger and\n * are wanted one row at a time, so they travel on request instead. This module\n * is the contract both ends of that request share, and it is deliberately free\n * of Node imports so a browser can hold it too.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugin/. */\nexport const API_BASE_PATH = 'context';\n\n/** The session a proxied request belongs to; the hub strips it before forwarding. */\nexport const SESSION_QUERY_PARAM = 'session';\n\nexport const ITEM_ROUTE = '/item';\n\nexport const NAME_QUERY_PARAM = 'name';\n\nexport const KIND_QUERY_PARAM = 'kind';\n\nexport const CONTEXT_DETAIL_VERSION = 1;\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,gBAAgB;;AAG7B,MAAa,sBAAsB;AAEnC,MAAa,aAAa;AAE1B,MAAa,mBAAmB;AAEhC,MAAa,mBAAmB;AAEhC,MAAa,yBAAyB"}
1
+ {"version":3,"file":"contextApi.cjs","names":[],"sources":["../../src/constants/contextApi.ts"],"sourcesContent":["/**\n * What one entry in the composition actually is, for a reader who clicked it.\n *\n * The projection prices the composition and stops there: names, owners, and\n * integer counts, small enough to republish whenever the composition changes.\n * The prose and the schema behind a figure are an order of magnitude larger and\n * are wanted one row at a time, so they travel on request instead. This module\n * is the contract both ends of that request share, and it is deliberately free\n * of Node imports so a browser can hold it too.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugins/. */\nexport const API_BASE_PATH = 'context';\n\n/** The session a proxied request belongs to; the hub strips it before forwarding. */\nexport const SESSION_QUERY_PARAM = 'session';\n\nexport const ITEM_ROUTE = '/item';\n\nexport const NAME_QUERY_PARAM = 'name';\n\nexport const KIND_QUERY_PARAM = 'kind';\n\nexport const CONTEXT_DETAIL_VERSION = 1;\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,gBAAgB;;AAG7B,MAAa,sBAAsB;AAEnC,MAAa,aAAa;AAE1B,MAAa,mBAAmB;AAEhC,MAAa,mBAAmB;AAEhC,MAAa,yBAAyB"}
@@ -9,7 +9,7 @@
9
9
  * is the contract both ends of that request share, and it is deliberately free
10
10
  * of Node imports so a browser can hold it too.
11
11
  */
12
- /** Where a host mounts this package's API; the segment after /api/plugin/. */
12
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
13
13
  export declare const API_BASE_PATH = "context";
14
14
  /** The session a proxied request belongs to; the hub strips it before forwarding. */
15
15
  export declare const SESSION_QUERY_PARAM = "session";
@@ -9,7 +9,7 @@
9
9
  * is the contract both ends of that request share, and it is deliberately free
10
10
  * of Node imports so a browser can hold it too.
11
11
  */
12
- /** Where a host mounts this package's API; the segment after /api/plugin/. */
12
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
13
13
  export declare const API_BASE_PATH = "context";
14
14
  /** The session a proxied request belongs to; the hub strips it before forwarding. */
15
15
  export declare const SESSION_QUERY_PARAM = "session";
@@ -9,7 +9,7 @@
9
9
  * is the contract both ends of that request share, and it is deliberately free
10
10
  * of Node imports so a browser can hold it too.
11
11
  */
12
- /** Where a host mounts this package's API; the segment after /api/plugin/. */
12
+ /** Where a host mounts this package's API; the segment after /api/plugins/. */
13
13
  const API_BASE_PATH = "context";
14
14
  /** The session a proxied request belongs to; the hub strips it before forwarding. */
15
15
  const SESSION_QUERY_PARAM = "session";
@@ -1 +1 @@
1
- {"version":3,"file":"contextApi.mjs","names":[],"sources":["../../src/constants/contextApi.ts"],"sourcesContent":["/**\n * What one entry in the composition actually is, for a reader who clicked it.\n *\n * The projection prices the composition and stops there: names, owners, and\n * integer counts, small enough to republish whenever the composition changes.\n * The prose and the schema behind a figure are an order of magnitude larger and\n * are wanted one row at a time, so they travel on request instead. This module\n * is the contract both ends of that request share, and it is deliberately free\n * of Node imports so a browser can hold it too.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugin/. */\nexport const API_BASE_PATH = 'context';\n\n/** The session a proxied request belongs to; the hub strips it before forwarding. */\nexport const SESSION_QUERY_PARAM = 'session';\n\nexport const ITEM_ROUTE = '/item';\n\nexport const NAME_QUERY_PARAM = 'name';\n\nexport const KIND_QUERY_PARAM = 'kind';\n\nexport const CONTEXT_DETAIL_VERSION = 1;\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,gBAAgB;;AAG7B,MAAa,sBAAsB;AAEnC,MAAa,aAAa;AAE1B,MAAa,mBAAmB;AAEhC,MAAa,mBAAmB;AAEhC,MAAa,yBAAyB"}
1
+ {"version":3,"file":"contextApi.mjs","names":[],"sources":["../../src/constants/contextApi.ts"],"sourcesContent":["/**\n * What one entry in the composition actually is, for a reader who clicked it.\n *\n * The projection prices the composition and stops there: names, owners, and\n * integer counts, small enough to republish whenever the composition changes.\n * The prose and the schema behind a figure are an order of magnitude larger and\n * are wanted one row at a time, so they travel on request instead. This module\n * is the contract both ends of that request share, and it is deliberately free\n * of Node imports so a browser can hold it too.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugins/. */\nexport const API_BASE_PATH = 'context';\n\n/** The session a proxied request belongs to; the hub strips it before forwarding. */\nexport const SESSION_QUERY_PARAM = 'session';\n\nexport const ITEM_ROUTE = '/item';\n\nexport const NAME_QUERY_PARAM = 'name';\n\nexport const KIND_QUERY_PARAM = 'kind';\n\nexport const CONTEXT_DETAIL_VERSION = 1;\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,gBAAgB;;AAG7B,MAAa,sBAAsB;AAEnC,MAAa,aAAa;AAE1B,MAAa,mBAAmB;AAEhC,MAAa,mBAAmB;AAEhC,MAAa,yBAAyB"}
@@ -1 +1 @@
1
- {"version":3,"file":"remote.cjs","names":[],"sources":["../../src/constants/remote.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\n\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\n\nexport const PROTOCOL_SOCKET_ROUTE = '/api/pi';\n\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\n\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-doompi_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'doompi_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\n\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\n\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\n\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-doompi-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7434;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 2_592_000;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAGxD,MAAa,oBAAoB,GAAG,iBAAiB;;AAGrD,MAAa,oBAAoB,GAAG,iBAAiB;AAEhB,GAAG,kBAAH;;AAKrC,MAAa,qBAAqB;;AAGlC,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,uBAAuB;;;;;;AAYpC,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AAExC,MAAa,4BAA4B;AAEzC,MAAa,+BAA+B;AAE5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAGnC,MAAa,yBAAyB;;AAGtC,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,8BAA8B"}
1
+ {"version":3,"file":"remote.cjs","names":[],"sources":["../../src/constants/remote.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\n\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\n\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\n\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-doompi_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'doompi_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\n\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\n\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\n\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-doompi-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7434;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 2_592_000;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAGxD,MAAa,oBAAoB,GAAG,iBAAiB;;AAGrD,MAAa,oBAAoB,GAAG,iBAAiB;AAEhB,GAAG,kBAAH;;AAKrC,MAAa,qBAAqB;;AAGlC,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,uBAAuB;;;;;;AAYpC,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AAExC,MAAa,4BAA4B;AAEzC,MAAa,+BAA+B;AAE5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAGnC,MAAa,yBAAyB;;AAGtC,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,8BAA8B"}
@@ -1 +1 @@
1
- {"version":3,"file":"remote.mjs","names":[],"sources":["../../src/constants/remote.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\n\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\n\nexport const PROTOCOL_SOCKET_ROUTE = '/api/pi';\n\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\n\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-doompi_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'doompi_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\n\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\n\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\n\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-doompi-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7434;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 2_592_000;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAGxD,MAAa,oBAAoB,GAAG,iBAAiB;;AAGrD,MAAa,oBAAoB,GAAG,iBAAiB;AAEhB,GAAG,kBAAH;;AAKrC,MAAa,qBAAqB;;AAGlC,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,uBAAuB;;;;;;AAYpC,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AAExC,MAAa,4BAA4B;AAEzC,MAAa,+BAA+B;AAE5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAGnC,MAAa,yBAAyB;;AAGtC,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,8BAA8B"}
1
+ {"version":3,"file":"remote.mjs","names":[],"sources":["../../src/constants/remote.ts"],"sourcesContent":["/**\n * Wire vocabulary for remote access, shared by the hub routes and the cockpit.\n *\n * Remote access binds a second loopback listener that a tunnel points at, and\n * everything arriving there must prove it holds a paired session. The\n * discriminator is the listening socket, not a header: a tunnel connects from\n * 127.0.0.1 like every local client, and there is no header a remote caller\n * cannot forge.\n */\n\n/** REST surface for the remote-access control plane; local callers only, except turn-off and revoke. */\nexport const REMOTE_API_ROUTE = '/api/remote';\n\n/** Direct endpoint that establishes one scoped end-to-end channel. */\nexport const REMOTE_CHANNEL_ROUTE = `${REMOTE_API_ROUTE}/channel`;\n\n/** The only authenticated HTTP gateway exposed on the tunnel listener. */\nexport const REMOTE_HTTP_ROUTE = `${REMOTE_API_ROUTE}/request`;\n\n/** Device-bound, sealed routes for ephemeral live Web Push registration. */\nexport const REMOTE_PUSH_ROUTE = `${REMOTE_API_ROUTE}/push`;\n\nexport const REMOTE_PUSH_KEY_ROUTE = `${REMOTE_PUSH_ROUTE}/key`;\n\nexport const PROTOCOL_SOCKET_ROUTE = '/api/ws';\n\n/** The pairing page a scanned QR opens. Unauthenticated on the tunnel listener. */\nexport const PAIRING_PAGE_ROUTE = '/pair';\n\n/** Where the pairing page posts the scanned code. Unauthenticated on the tunnel listener. */\nexport const PAIRING_CLAIM_ROUTE = '/api/remote/pair';\n\n/** Polled until the host approves or denies. Unauthenticated on the tunnel listener. */\nexport const PAIRING_STATUS_ROUTE = '/api/remote/pair/status';\n\n/** Query parameter naming the pairing request; a path parameter would force a wildcard into the allowlist. */\nexport const PAIRING_STATUS_QUERY = 'request';\n\n/** URL-fragment fields authenticated by seeing the host's physical QR. */\nexport const BUNDLE_SIGNING_KEY_PARAM = 's';\n\nexport const BUNDLE_MINIMUM_REVISION_PARAM = 'r';\n\n/**\n * Session cookie base name. Rendered `__Host-doompi_device` by hono's `host`\n * prefix, which refuses the cookie unless it is Secure, Path=/, and\n * Domain-less, so a sibling subdomain can neither read nor overwrite it.\n */\nexport const DEVICE_COOKIE = 'doompi_device';\n\n/**\n * Passkey ceremonies exposed directly on the pairing surface.\n *\n * Sign-in is unauthenticated because it creates a session. Registration requires\n * the paired device cookie issued after host approval, but remains direct so the\n * self-contained pairing page can offer enrolment before loading the cockpit.\n */\nexport const PASSKEY_AUTH_BEGIN_ROUTE = '/api/remote/passkeys/authenticate/begin';\n\nexport const PASSKEY_AUTH_FINISH_ROUTE = '/api/remote/passkeys/authenticate/finish';\n\nexport const PASSKEY_REGISTER_BEGIN_ROUTE = '/api/remote/passkeys/register/begin';\n\nexport const PASSKEY_REGISTER_FINISH_ROUTE = '/api/remote/passkeys/register/finish';\n\n/** Header carrying a step-up assertion for an action that needs more than a live session. */\nexport const STEP_UP_HEADER = 'x-doompi-assertion';\n\n/** The vite dev server port; the origin allowlist and vite.config.ts share this. */\nexport const WEB_DEV_SERVER_PORT = 7434;\n\n/** How long a scanned pairing code stays claimable. */\nexport const PAIRING_CODE_TTL_MS = 120_000;\n\n/** How long the host has to approve or deny a claimed request. */\nexport const PAIRING_REQUEST_TTL_MS = 180_000;\n\n/** Cookie lifetime when session expiry is switched off; a session cookie can outlive the laptop. */\nexport const COOKIE_CEILING_SECONDS = 2_592_000;\n\n/** Hub frame announcing remote-access state; sent to local and remote pages. */\nexport const REMOTE_STATE_TYPE = 'remote_state';\n\n/** Hub frame announcing a device waiting for approval; sent to local pages only. */\nexport const REMOTE_PAIRING_REQUEST_TYPE = 'remote_pairing_request';\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,GAAG,iBAAiB;;AAGxD,MAAa,oBAAoB,GAAG,iBAAiB;;AAGrD,MAAa,oBAAoB,GAAG,iBAAiB;AAEhB,GAAG,kBAAH;;AAKrC,MAAa,qBAAqB;;AAGlC,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,uBAAuB;;;;;;AAYpC,MAAa,gBAAgB;;;;;;;;AAS7B,MAAa,2BAA2B;AAExC,MAAa,4BAA4B;AAEzC,MAAa,+BAA+B;AAE5C,MAAa,gCAAgC;;AAG7C,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,sBAAsB;;AAGnC,MAAa,yBAAyB;;AAGtC,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,8BAA8B"}
@@ -1,4 +1,4 @@
1
- import { ContextDetailFile, ContextItemDetail, ContextItemKind, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail } from "./types-context-api.cjs";
1
+ import { ContextDetailFile, ContextItemDetail, ContextItemKind, ContextPromptDetail, ContextPromptStage, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail } from "./types-context-api.cjs";
2
2
  import { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM } from "./constants/contextApi.cjs";
3
3
  import { itemDetailUrl } from "./services/contextApiUrls/index.cjs";
4
- export { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ContextDetailFile, ContextItemDetail, ContextItemKind, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM, itemDetailUrl };
4
+ export { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ContextDetailFile, ContextItemDetail, ContextItemKind, ContextPromptDetail, ContextPromptStage, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM, itemDetailUrl };
@@ -1,4 +1,4 @@
1
- import { ContextDetailFile, ContextItemDetail, ContextItemKind, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail } from "./types-context-api.mjs";
1
+ import { ContextDetailFile, ContextItemDetail, ContextItemKind, ContextPromptDetail, ContextPromptStage, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail } from "./types-context-api.mjs";
2
2
  import { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM } from "./constants/contextApi.mjs";
3
3
  import { itemDetailUrl } from "./services/contextApiUrls/index.mjs";
4
- export { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ContextDetailFile, ContextItemDetail, ContextItemKind, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM, itemDetailUrl };
4
+ export { API_BASE_PATH, CONTEXT_DETAIL_VERSION, ContextDetailFile, ContextItemDetail, ContextItemKind, ContextPromptDetail, ContextPromptStage, ContextSkillDetail, ContextTokenBreakdown, ContextToolDetail, ITEM_ROUTE, KIND_QUERY_PARAM, NAME_QUERY_PARAM, SESSION_QUERY_PARAM, itemDetailUrl };
@@ -49,6 +49,13 @@ function buildContextDetail(input) {
49
49
  ...skill.filePath === void 0 ? {} : { filePath: skill.filePath },
50
50
  modelInvocable: skill.modelInvocable
51
51
  });
52
+ if (input.systemPrompt !== void 0) items.push({
53
+ itemKind: "prompt",
54
+ name: "system",
55
+ tokens: input.systemPrompt.tokens,
56
+ stage: input.systemPrompt.stage,
57
+ text: input.systemPrompt.text
58
+ });
52
59
  return items;
53
60
  }
54
61
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-detail.cjs","names":["tokensForTool"],"sources":["../src/services/contextDetail/index.ts"],"sourcesContent":["import type { ContextItemDetail, ContextToolDetail } from '../../types/contextApi';\nimport type { ContextSkillInventory } from '../contextProjection';\nimport { type CountTokens, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * The prose and schema behind the projection's figures.\n *\n * Built from the same inventory in the same pass, so a detail can never\n * describe a composition the panel is not showing. Nothing is grouped here:\n * the panel already knows which mode a row sits under, and a lookup by name is\n * all a click needs.\n */\n\nconst MCP_KEY_PREFIX = 'mcp:';\nconst CORE_OWNER = 'pi';\n\n/** The same join the projection makes, so a row and its detail agree on the owner. */\nfunction ownerOf(source: ToolSource): string {\n if (source.kind === 'extension') return source.packageName ?? source.key;\n if (source.kind === 'mcp') return source.key.slice(MCP_KEY_PREFIX.length);\n return CORE_OWNER;\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemDetail['source'] {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nexport interface ContextDetailInput {\n readonly sources: readonly ToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n readonly countTokens: CountTokens;\n}\n\nexport function buildContextDetail(input: ContextDetailInput): ContextItemDetail[] {\n const items: ContextItemDetail[] = [];\n\n for (const source of input.sources) {\n const owner = ownerOf(source);\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const detail: ContextToolDetail = {\n itemKind: 'tool',\n name: tool.name,\n owner,\n source: source.kind === 'core' ? 'core' : source.kind,\n active: tool.active,\n tokens: { schemaTokens: cost.schemaTokens, promptTokens: cost.promptTokens, totalTokens: cost.totalTokens },\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n };\n items.push(detail);\n }\n }\n\n for (const skill of input.skills) {\n items.push({\n itemKind: 'skill',\n name: skill.name,\n owner: skill.owner,\n source: skillSource(skill.group),\n active: true,\n tokens: skill.promptTokens ?? 0,\n description: skill.description,\n ...(skill.filePath === undefined ? {} : { filePath: skill.filePath }),\n modelInvocable: skill.modelInvocable,\n });\n }\n\n return items;\n}\n"],"mappings":";;;AAcA,MAAM,aAAa;;AAGnB,SAAS,QAAQ,QAA4B;CAC3C,IAAI,OAAO,SAAS,aAAa,OAAO,OAAO,eAAe,OAAO;CACrE,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,MAAM,CAAqB;CACxE,OAAO;AACT;AAEA,SAAS,YAAY,OAAoE;CACvF,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAQA,SAAgB,mBAAmB,OAAgD;CACjF,MAAM,QAA6B,CAAC;CAEpC,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,QAAQ,QAAQ,MAAM;EAC5B,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAOA,cAAAA,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,SAA4B;IAChC,UAAU;IACV,MAAM,KAAK;IACX;IACA,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD,QAAQ,KAAK;IACb,QAAQ;KAAE,cAAc,KAAK;KAAc,cAAc,KAAK;KAAc,aAAa,KAAK;IAAY;IAC1G,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;IAC5D,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;IAClE,GAAI,KAAK,kBAAkB,SAAS,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;IACnF,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACzE;GACA,MAAM,KAAK,MAAM;EACnB;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MAAM,KAAK;EACT,UAAU;EACV,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,QAAQ,YAAY,MAAM,KAAK;EAC/B,QAAQ;EACR,QAAQ,MAAM,gBAAgB;EAC9B,aAAa,MAAM;EACnB,GAAI,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;EACnE,gBAAgB,MAAM;CACxB,CAAC;CAGH,OAAO;AACT"}
1
+ {"version":3,"file":"context-detail.cjs","names":["tokensForTool"],"sources":["../src/services/contextDetail/index.ts"],"sourcesContent":["import type { ContextItemDetail, ContextPromptStage, ContextToolDetail } from '../../types/contextApi';\nimport type { ContextItemSource, ContextSkillInventory } from '../contextProjection';\nimport { type CountTokens, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * The prose and schema behind the projection's figures.\n *\n * Built from the same inventory in the same pass, so a detail can never\n * describe a composition the panel is not showing. Nothing is grouped here:\n * the panel already knows which mode a row sits under, and a lookup by name is\n * all a click needs.\n */\n\nconst MCP_KEY_PREFIX = 'mcp:';\nconst CORE_OWNER = 'pi';\n\n/** The same join the projection makes, so a row and its detail agree on the owner. */\nfunction ownerOf(source: ToolSource): string {\n if (source.kind === 'extension') return source.packageName ?? source.key;\n if (source.kind === 'mcp') return source.key.slice(MCP_KEY_PREFIX.length);\n return CORE_OWNER;\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nexport interface ContextDetailInput {\n readonly sources: readonly ToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n readonly countTokens: CountTokens;\n /** The assembled prompt, when the host has one to report. */\n readonly systemPrompt?: { readonly text: string; readonly tokens: number; readonly stage: ContextPromptStage };\n}\n\nexport function buildContextDetail(input: ContextDetailInput): ContextItemDetail[] {\n const items: ContextItemDetail[] = [];\n\n for (const source of input.sources) {\n const owner = ownerOf(source);\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const detail: ContextToolDetail = {\n itemKind: 'tool',\n name: tool.name,\n owner,\n source: source.kind === 'core' ? 'core' : source.kind,\n active: tool.active,\n tokens: { schemaTokens: cost.schemaTokens, promptTokens: cost.promptTokens, totalTokens: cost.totalTokens },\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n };\n items.push(detail);\n }\n }\n\n for (const skill of input.skills) {\n items.push({\n itemKind: 'skill',\n name: skill.name,\n owner: skill.owner,\n source: skillSource(skill.group),\n active: true,\n tokens: skill.promptTokens ?? 0,\n description: skill.description,\n ...(skill.filePath === undefined ? {} : { filePath: skill.filePath }),\n modelInvocable: skill.modelInvocable,\n });\n }\n\n if (input.systemPrompt !== undefined) {\n items.push({\n itemKind: 'prompt',\n name: 'system',\n tokens: input.systemPrompt.tokens,\n stage: input.systemPrompt.stage,\n text: input.systemPrompt.text,\n });\n }\n\n return items;\n}\n"],"mappings":";;;AAcA,MAAM,aAAa;;AAGnB,SAAS,QAAQ,QAA4B;CAC3C,IAAI,OAAO,SAAS,aAAa,OAAO,OAAO,eAAe,OAAO;CACrE,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,MAAM,CAAqB;CACxE,OAAO;AACT;AAEA,SAAS,YAAY,OAA0D;CAC7E,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAUA,SAAgB,mBAAmB,OAAgD;CACjF,MAAM,QAA6B,CAAC;CAEpC,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,QAAQ,QAAQ,MAAM;EAC5B,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAOA,cAAAA,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,SAA4B;IAChC,UAAU;IACV,MAAM,KAAK;IACX;IACA,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD,QAAQ,KAAK;IACb,QAAQ;KAAE,cAAc,KAAK;KAAc,cAAc,KAAK;KAAc,aAAa,KAAK;IAAY;IAC1G,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;IAC5D,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;IAClE,GAAI,KAAK,kBAAkB,SAAS,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;IACnF,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACzE;GACA,MAAM,KAAK,MAAM;EACnB;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MAAM,KAAK;EACT,UAAU;EACV,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,QAAQ,YAAY,MAAM,KAAK;EAC/B,QAAQ;EACR,QAAQ,MAAM,gBAAgB;EAC9B,aAAa,MAAM;EACnB,GAAI,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;EACnE,gBAAgB,MAAM;CACxB,CAAC;CAGH,IAAI,MAAM,iBAAiB,KAAA,GACzB,MAAM,KAAK;EACT,UAAU;EACV,MAAM;EACN,QAAQ,MAAM,aAAa;EAC3B,OAAO,MAAM,aAAa;EAC1B,MAAM,MAAM,aAAa;CAC3B,CAAC;CAGH,OAAO;AACT"}
@@ -1,12 +1,18 @@
1
1
  import { CountTokens } from "./services/toolCost/index.cjs";
2
2
  import { ToolSource } from "./tool-inventory.cjs";
3
3
  import { ContextSkillInventory } from "./context-projection.cjs";
4
- import { ContextItemDetail } from "./types-context-api.cjs";
4
+ import { ContextItemDetail, ContextPromptStage } from "./types-context-api.cjs";
5
5
  //#region src/services/contextDetail/index.d.ts
6
6
  export interface ContextDetailInput {
7
7
  readonly sources: readonly ToolSource[];
8
8
  readonly skills: readonly ContextSkillInventory[];
9
9
  readonly countTokens: CountTokens;
10
+ /** The assembled prompt, when the host has one to report. */
11
+ readonly systemPrompt?: {
12
+ readonly text: string;
13
+ readonly tokens: number;
14
+ readonly stage: ContextPromptStage;
15
+ };
10
16
  }
11
17
  export declare function buildContextDetail(input: ContextDetailInput): ContextItemDetail[];
12
18
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-detail.d.cts","names":[],"sources":["../src/services/contextDetail/index.ts"],"mappings":";;;;;iBA6BiB;WACN,kBAAkB;WAClB,iBAAiB;WACjB,aAAa;;wBAGR,mBAAmB,OAAO,qBAAqB"}
1
+ {"version":3,"file":"context-detail.d.cts","names":[],"sources":["../src/services/contextDetail/index.ts"],"mappings":";;;;;iBA6BiB;WACN,kBAAkB;WAClB,iBAAiB;WACjB,aAAa;;WAEb;aAA0B;aAAuB;aAAyB,OAAO;;;wBAG5E,mBAAmB,OAAO,qBAAqB"}
@@ -1,12 +1,18 @@
1
1
  import { CountTokens } from "./services/toolCost/index.mjs";
2
2
  import { ToolSource } from "./tool-inventory.mjs";
3
3
  import { ContextSkillInventory } from "./context-projection.mjs";
4
- import { ContextItemDetail } from "./types-context-api.mjs";
4
+ import { ContextItemDetail, ContextPromptStage } from "./types-context-api.mjs";
5
5
  //#region src/services/contextDetail/index.d.ts
6
6
  export interface ContextDetailInput {
7
7
  readonly sources: readonly ToolSource[];
8
8
  readonly skills: readonly ContextSkillInventory[];
9
9
  readonly countTokens: CountTokens;
10
+ /** The assembled prompt, when the host has one to report. */
11
+ readonly systemPrompt?: {
12
+ readonly text: string;
13
+ readonly tokens: number;
14
+ readonly stage: ContextPromptStage;
15
+ };
10
16
  }
11
17
  export declare function buildContextDetail(input: ContextDetailInput): ContextItemDetail[];
12
18
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-detail.d.mts","names":[],"sources":["../src/services/contextDetail/index.ts"],"mappings":";;;;;iBA6BiB;WACN,kBAAkB;WAClB,iBAAiB;WACjB,aAAa;;wBAGR,mBAAmB,OAAO,qBAAqB"}
1
+ {"version":3,"file":"context-detail.d.mts","names":[],"sources":["../src/services/contextDetail/index.ts"],"mappings":";;;;;iBA6BiB;WACN,kBAAkB;WAClB,iBAAiB;WACjB,aAAa;;WAEb;aAA0B;aAAuB;aAAyB,OAAO;;;wBAG5E,mBAAmB,OAAO,qBAAqB"}
@@ -48,6 +48,13 @@ function buildContextDetail(input) {
48
48
  ...skill.filePath === void 0 ? {} : { filePath: skill.filePath },
49
49
  modelInvocable: skill.modelInvocable
50
50
  });
51
+ if (input.systemPrompt !== void 0) items.push({
52
+ itemKind: "prompt",
53
+ name: "system",
54
+ tokens: input.systemPrompt.tokens,
55
+ stage: input.systemPrompt.stage,
56
+ text: input.systemPrompt.text
57
+ });
51
58
  return items;
52
59
  }
53
60
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-detail.mjs","names":[],"sources":["../src/services/contextDetail/index.ts"],"sourcesContent":["import type { ContextItemDetail, ContextToolDetail } from '../../types/contextApi';\nimport type { ContextSkillInventory } from '../contextProjection';\nimport { type CountTokens, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * The prose and schema behind the projection's figures.\n *\n * Built from the same inventory in the same pass, so a detail can never\n * describe a composition the panel is not showing. Nothing is grouped here:\n * the panel already knows which mode a row sits under, and a lookup by name is\n * all a click needs.\n */\n\nconst MCP_KEY_PREFIX = 'mcp:';\nconst CORE_OWNER = 'pi';\n\n/** The same join the projection makes, so a row and its detail agree on the owner. */\nfunction ownerOf(source: ToolSource): string {\n if (source.kind === 'extension') return source.packageName ?? source.key;\n if (source.kind === 'mcp') return source.key.slice(MCP_KEY_PREFIX.length);\n return CORE_OWNER;\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemDetail['source'] {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nexport interface ContextDetailInput {\n readonly sources: readonly ToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n readonly countTokens: CountTokens;\n}\n\nexport function buildContextDetail(input: ContextDetailInput): ContextItemDetail[] {\n const items: ContextItemDetail[] = [];\n\n for (const source of input.sources) {\n const owner = ownerOf(source);\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const detail: ContextToolDetail = {\n itemKind: 'tool',\n name: tool.name,\n owner,\n source: source.kind === 'core' ? 'core' : source.kind,\n active: tool.active,\n tokens: { schemaTokens: cost.schemaTokens, promptTokens: cost.promptTokens, totalTokens: cost.totalTokens },\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n };\n items.push(detail);\n }\n }\n\n for (const skill of input.skills) {\n items.push({\n itemKind: 'skill',\n name: skill.name,\n owner: skill.owner,\n source: skillSource(skill.group),\n active: true,\n tokens: skill.promptTokens ?? 0,\n description: skill.description,\n ...(skill.filePath === undefined ? {} : { filePath: skill.filePath }),\n modelInvocable: skill.modelInvocable,\n });\n }\n\n return items;\n}\n"],"mappings":";;AAcA,MAAM,aAAa;;AAGnB,SAAS,QAAQ,QAA4B;CAC3C,IAAI,OAAO,SAAS,aAAa,OAAO,OAAO,eAAe,OAAO;CACrE,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,MAAM,CAAqB;CACxE,OAAO;AACT;AAEA,SAAS,YAAY,OAAoE;CACvF,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAQA,SAAgB,mBAAmB,OAAgD;CACjF,MAAM,QAA6B,CAAC;CAEpC,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,QAAQ,QAAQ,MAAM;EAC5B,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAO,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,SAA4B;IAChC,UAAU;IACV,MAAM,KAAK;IACX;IACA,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD,QAAQ,KAAK;IACb,QAAQ;KAAE,cAAc,KAAK;KAAc,cAAc,KAAK;KAAc,aAAa,KAAK;IAAY;IAC1G,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;IAC5D,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;IAClE,GAAI,KAAK,kBAAkB,SAAS,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;IACnF,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACzE;GACA,MAAM,KAAK,MAAM;EACnB;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MAAM,KAAK;EACT,UAAU;EACV,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,QAAQ,YAAY,MAAM,KAAK;EAC/B,QAAQ;EACR,QAAQ,MAAM,gBAAgB;EAC9B,aAAa,MAAM;EACnB,GAAI,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;EACnE,gBAAgB,MAAM;CACxB,CAAC;CAGH,OAAO;AACT"}
1
+ {"version":3,"file":"context-detail.mjs","names":[],"sources":["../src/services/contextDetail/index.ts"],"sourcesContent":["import type { ContextItemDetail, ContextPromptStage, ContextToolDetail } from '../../types/contextApi';\nimport type { ContextItemSource, ContextSkillInventory } from '../contextProjection';\nimport { type CountTokens, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * The prose and schema behind the projection's figures.\n *\n * Built from the same inventory in the same pass, so a detail can never\n * describe a composition the panel is not showing. Nothing is grouped here:\n * the panel already knows which mode a row sits under, and a lookup by name is\n * all a click needs.\n */\n\nconst MCP_KEY_PREFIX = 'mcp:';\nconst CORE_OWNER = 'pi';\n\n/** The same join the projection makes, so a row and its detail agree on the owner. */\nfunction ownerOf(source: ToolSource): string {\n if (source.kind === 'extension') return source.packageName ?? source.key;\n if (source.kind === 'mcp') return source.key.slice(MCP_KEY_PREFIX.length);\n return CORE_OWNER;\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nexport interface ContextDetailInput {\n readonly sources: readonly ToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n readonly countTokens: CountTokens;\n /** The assembled prompt, when the host has one to report. */\n readonly systemPrompt?: { readonly text: string; readonly tokens: number; readonly stage: ContextPromptStage };\n}\n\nexport function buildContextDetail(input: ContextDetailInput): ContextItemDetail[] {\n const items: ContextItemDetail[] = [];\n\n for (const source of input.sources) {\n const owner = ownerOf(source);\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const detail: ContextToolDetail = {\n itemKind: 'tool',\n name: tool.name,\n owner,\n source: source.kind === 'core' ? 'core' : source.kind,\n active: tool.active,\n tokens: { schemaTokens: cost.schemaTokens, promptTokens: cost.promptTokens, totalTokens: cost.totalTokens },\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n };\n items.push(detail);\n }\n }\n\n for (const skill of input.skills) {\n items.push({\n itemKind: 'skill',\n name: skill.name,\n owner: skill.owner,\n source: skillSource(skill.group),\n active: true,\n tokens: skill.promptTokens ?? 0,\n description: skill.description,\n ...(skill.filePath === undefined ? {} : { filePath: skill.filePath }),\n modelInvocable: skill.modelInvocable,\n });\n }\n\n if (input.systemPrompt !== undefined) {\n items.push({\n itemKind: 'prompt',\n name: 'system',\n tokens: input.systemPrompt.tokens,\n stage: input.systemPrompt.stage,\n text: input.systemPrompt.text,\n });\n }\n\n return items;\n}\n"],"mappings":";;AAcA,MAAM,aAAa;;AAGnB,SAAS,QAAQ,QAA4B;CAC3C,IAAI,OAAO,SAAS,aAAa,OAAO,OAAO,eAAe,OAAO;CACrE,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI,MAAM,CAAqB;CACxE,OAAO;AACT;AAEA,SAAS,YAAY,OAA0D;CAC7E,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAUA,SAAgB,mBAAmB,OAAgD;CACjF,MAAM,QAA6B,CAAC;CAEpC,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,QAAQ,QAAQ,MAAM;EAC5B,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAO,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,SAA4B;IAChC,UAAU;IACV,MAAM,KAAK;IACX;IACA,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD,QAAQ,KAAK;IACb,QAAQ;KAAE,cAAc,KAAK;KAAc,cAAc,KAAK;KAAc,aAAa,KAAK;IAAY;IAC1G,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;IAC5D,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;IAClE,GAAI,KAAK,kBAAkB,SAAS,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;IACnF,GAAI,KAAK,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,KAAK,WAAW;GACzE;GACA,MAAM,KAAK,MAAM;EACnB;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MAAM,KAAK;EACT,UAAU;EACV,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,QAAQ,YAAY,MAAM,KAAK;EAC/B,QAAQ;EACR,QAAQ,MAAM,gBAAgB;EAC9B,aAAa,MAAM;EACnB,GAAI,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;EACnE,gBAAgB,MAAM;CACxB,CAAC;CAGH,IAAI,MAAM,iBAAiB,KAAA,GACzB,MAAM,KAAK;EACT,UAAU;EACV,MAAM;EACN,QAAQ,MAAM,aAAa;EAC3B,OAAO,MAAM,aAAa;EAC1B,MAAM,MAAM,aAAa;CAC3B,CAAC;CAGH,OAAO;AACT"}
@@ -108,6 +108,7 @@ function projectContext(input) {
108
108
  groups,
109
109
  totalTokens: groups.reduce((total, group) => total + group.tokens, 0),
110
110
  inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),
111
+ ...input.systemPrompt === void 0 ? {} : { systemPrompt: input.systemPrompt },
111
112
  estimator: CONTEXT_PROJECTION_ESTIMATOR
112
113
  };
113
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context-projection.cjs","names":["tokensForTool"],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in doompi-web. */\nexport const DOOM_CONTEXT_ENTRY_TYPE = 'doom-context';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, core: 3 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAa,6BAA6B;;AAG1C,MAAa,0BAA0B;;AAGvC,MAAa,+BAA+B;;AAM5C,MAAM,aAAa;;AAEnB,MAAM,aAAa;AAGnB,MAAM,aAA+C;CAAE,OAAO;CAAG,OAAO;CAAG,QAAQ;CAAG,MAAM;AAAE;AAC9F,MAAM,eAAkD;CAAE,WAAW;CAAG,KAAK;CAAG,QAAQ;CAAG,MAAM;AAAE;AA4FnG,SAAS,YAAY,OAA0D;CAC7E,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAEA,SAAS,MAAM,MAA6B,OAAsC;CAChF,OAAO,aAAa,KAAK,UAAU,aAAa,MAAM,WAAW,KAAK,KAAK,cAAc,MAAM,IAAI;AACrG;AAEA,SAAgB,eAAe,OAAkD;CAC/E,MAAM,0BAAU,IAAI,IAAoB;CAKxC,MAAM,WAAW,IAAY,OAAe,SAAiC;EAC3E,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,QAAQ,IAAI,IAAI;GAAE;GAAM;GAAO,OAAO,CAAC;EAAE,CAAC;CAClE;CACA,IAAI,MAAM,WAAW,QAAQ,MAAM,WAAW,MAAM,WAAW,OAAO;CACtE,KAAK,MAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,IAAI,MAAM,OAAO,MAAM,IAAI;CAI3E,KAAK,MAAM,UAAU,MAAM,SAAS,QAAQ,QAAQ,QAAQ,QAAQ;CAEpE,MAAM,SAAS,OAAe,MAA6B,gBAAsD;EAC/G,MAAM,aAAa,eAAe,MAAM,YAAY;EACpD,MAAM,KAAK,YAAY,QAAQ;EAC/B,MAAM,OAAO,YAAY,QAAQ;EACjC,QAAQ,IAAI,cAAc,WAAW,aAAc,WAAW,SAAS,KAAM,IAAI,IAAI;EACrF,QAAQ,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI;CAClC;CAEA,KAAK,MAAM,UAAU,MAAM,SAAS;EAIlC,MAAM,QACJ,OAAO,SAAS,cACX,OAAO,eAAe,OAAO,MAC9B,OAAO,SAAS,QACd,OAAO,IAAI,MAAM,CAAqB,IACtC;EACR,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAOA,cAAAA,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,OAA8B;IAClC,MAAM,KAAK;IACX,UAAU;IACV,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD;IACA,QAAQ,KAAK;IACb,QAAQ,KAAK;GACf;GACA,IAAI,OAAO,SAAS,QAAQ;IAC1B,QAAQ,YAAY,YAAY,MAAM;IACtC,QAAQ,IAAI,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;GAC1C,OACE,MAAM,OAAO,MAAM,KAAK,kBAAkB;EAE9C;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MACE,MAAM,OACN;EACE,MAAM,MAAM;EACZ,UAAU;EACV,QAAQ,YAAY,MAAM,KAAK;EAC/B,OAAO,MAAM;EACb,QAAQ,MAAM,gBAAgB;EAG9B,QAAQ;CACV,GACA,MAAM,kBACR;CAGF,MAAM,SAAmC,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,CAC5D,KAAK,CAAC,IAAI,YAAY;EACrB,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK;EAI1C,OAAO;GACL;GACA,OAAO,OAAO;GACd,MAAM,OAAO;GACb;GACA,QAAQ,MAAM,QAAQ,OAAO,SAAS,SAAS,KAAK,SAAS,KAAK,SAAS,IAAI,CAAC;GAChF,gBAAgB,MAAM,QAAQ,OAAO,SAAS,SAAS,KAAK,SAAS,IAAI,KAAK,SAAS,CAAC;EAC1F;CACF,CAAC,CAAC,CACD,MAAM,MAAM,UAAU,WAAW,KAAK,QAAQ,WAAW,MAAM,SAAS,KAAK,MAAM,cAAc,MAAM,KAAK,CAAC;CAEhH,OAAO;EACL,SAAA;EACA,UAAU,MAAM;EAChB,WAAW;GACT,WAAW,MAAM;GACjB,SAAS,CAAC,GAAG,MAAM,OAAO;GAC1B,GAAI,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;EAClE;EACA;EACA,aAAa,OAAO,QAAQ,OAAO,UAAU,QAAQ,MAAM,QAAQ,CAAC;EACpE,gBAAgB,OAAO,QAAQ,OAAO,UAAU,QAAQ,MAAM,gBAAgB,CAAC;EAC/E,WAAW;CACb;AACF"}
1
+ {"version":3,"file":"context-projection.cjs","names":["tokensForTool"],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in doompi-web. */\nexport const DOOM_CONTEXT_ENTRY_TYPE = 'doom-context';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, core: 3 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { CountTokens } from "./services/toolCost/index.cjs";
2
2
  import { ToolEntry, ToolSource } from "./tool-inventory.cjs";
3
+ import { ContextPromptStage } from "./types-context-api.cjs";
3
4
  //#region src/services/contextProjection/index.d.ts
4
5
  /** What admitted a package into the composition. */
5
6
  export interface PackageAttribution {
@@ -47,6 +48,17 @@ export interface ContextGroupProjection {
47
48
  /** What its gated items would add if switched on. */
48
49
  readonly inactiveTokens: number;
49
50
  }
51
+ /**
52
+ * What the assembled system prompt costs, and how complete the figure is.
53
+ *
54
+ * Kept out of `groups` on purpose. Every tool's prose and every skill's listing
55
+ * already count inside the group subtotals, and those are slices of this same
56
+ * text, so folding it into `totalTokens` would bill them twice.
57
+ */
58
+ export interface ContextSystemPromptProjection {
59
+ readonly tokens: number;
60
+ readonly stage: ContextPromptStage;
61
+ }
50
62
  export interface ContextProjection {
51
63
  readonly version: typeof CONTEXT_PROJECTION_VERSION;
52
64
  readonly revision: number;
@@ -59,6 +71,7 @@ export interface ContextProjection {
59
71
  readonly groups: readonly ContextGroupProjection[];
60
72
  readonly totalTokens: number;
61
73
  readonly inactiveTokens: number;
74
+ readonly systemPrompt?: ContextSystemPromptProjection;
62
75
  readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;
63
76
  }
64
77
  export interface ContextConditionalAttribution {
@@ -107,6 +120,8 @@ export interface ContextProjectionInput {
107
120
  */
108
121
  readonly attribution: Readonly<Record<string, PackageAttribution>>;
109
122
  readonly countTokens: CountTokens;
123
+ /** Absent until the session has a prompt to report. */
124
+ readonly systemPrompt?: ContextSystemPromptProjection;
110
125
  }
111
126
  export declare function projectContext(input: ContextProjectionInput): ContextProjection;
112
127
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-projection.d.cts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAgBW;;qBAGA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;iBAGM;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;WAEF,iBAAiB;WACjB;WACA;WACA,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;wBAmBR,eAAe,OAAO,yBAAyB"}
1
+ {"version":3,"file":"context-projection.d.cts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAiBW;;qBAGA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;;;;;;;;iBAUM;WACN;WACA,OAAO;;iBAGD;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;WAEF,iBAAiB;WACjB;WACA;WACA,eAAe;WACf,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;WAEb,eAAe;;wBAmBV,eAAe,OAAO,yBAAyB"}
@@ -1,5 +1,6 @@
1
1
  import { CountTokens } from "./services/toolCost/index.mjs";
2
2
  import { ToolEntry, ToolSource } from "./tool-inventory.mjs";
3
+ import { ContextPromptStage } from "./types-context-api.mjs";
3
4
  //#region src/services/contextProjection/index.d.ts
4
5
  /** What admitted a package into the composition. */
5
6
  export interface PackageAttribution {
@@ -47,6 +48,17 @@ export interface ContextGroupProjection {
47
48
  /** What its gated items would add if switched on. */
48
49
  readonly inactiveTokens: number;
49
50
  }
51
+ /**
52
+ * What the assembled system prompt costs, and how complete the figure is.
53
+ *
54
+ * Kept out of `groups` on purpose. Every tool's prose and every skill's listing
55
+ * already count inside the group subtotals, and those are slices of this same
56
+ * text, so folding it into `totalTokens` would bill them twice.
57
+ */
58
+ export interface ContextSystemPromptProjection {
59
+ readonly tokens: number;
60
+ readonly stage: ContextPromptStage;
61
+ }
50
62
  export interface ContextProjection {
51
63
  readonly version: typeof CONTEXT_PROJECTION_VERSION;
52
64
  readonly revision: number;
@@ -59,6 +71,7 @@ export interface ContextProjection {
59
71
  readonly groups: readonly ContextGroupProjection[];
60
72
  readonly totalTokens: number;
61
73
  readonly inactiveTokens: number;
74
+ readonly systemPrompt?: ContextSystemPromptProjection;
62
75
  readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;
63
76
  }
64
77
  export interface ContextConditionalAttribution {
@@ -107,6 +120,8 @@ export interface ContextProjectionInput {
107
120
  */
108
121
  readonly attribution: Readonly<Record<string, PackageAttribution>>;
109
122
  readonly countTokens: CountTokens;
123
+ /** Absent until the session has a prompt to report. */
124
+ readonly systemPrompt?: ContextSystemPromptProjection;
110
125
  }
111
126
  export declare function projectContext(input: ContextProjectionInput): ContextProjection;
112
127
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"context-projection.d.mts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAgBW;;qBAGA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;iBAGM;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;WAEF,iBAAiB;WACjB;WACA;WACA,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;wBAmBR,eAAe,OAAO,yBAAyB"}
1
+ {"version":3,"file":"context-projection.d.mts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAiBW;;qBAGA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;;;;;;;;iBAUM;WACN;WACA,OAAO;;iBAGD;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;WAEF,iBAAiB;WACjB;WACA;WACA,eAAe;WACf,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;WAEb,eAAe;;wBAmBV,eAAe,OAAO,yBAAyB"}
@@ -107,6 +107,7 @@ function projectContext(input) {
107
107
  groups,
108
108
  totalTokens: groups.reduce((total, group) => total + group.tokens, 0),
109
109
  inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),
110
+ ...input.systemPrompt === void 0 ? {} : { systemPrompt: input.systemPrompt },
110
111
  estimator: CONTEXT_PROJECTION_ESTIMATOR
111
112
  };
112
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context-projection.mjs","names":[],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in doompi-web. */\nexport const DOOM_CONTEXT_ENTRY_TYPE = 'doom-context';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, core: 3 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context-projection.mjs","names":[],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in doompi-web. */\nexport const DOOM_CONTEXT_ENTRY_TYPE = 'doom-context';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, core: 3 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}