@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18

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 (357) hide show
  1. package/README.md +134 -15
  2. package/assets/integrations/skills/pappra/SKILL.md +28 -7
  3. package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
  4. package/assets/integrations/skills/pappra/references/environment.md +360 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +20 -0
  6. package/assets/integrations/skills/pappra/references/operations.md +36 -5
  7. package/dist/adapters/pi/recovery-extension.d.ts +65 -0
  8. package/dist/adapters/pi/recovery-extension.js +238 -0
  9. package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
  10. package/dist/adapters/pi/session-transition-extension.js +265 -0
  11. package/dist/app/automatic-handoff-events.d.ts +26 -0
  12. package/dist/app/automatic-handoff-events.js +134 -0
  13. package/dist/app/bounded-file.d.ts +2 -0
  14. package/dist/app/bounded-file.js +2 -0
  15. package/dist/app/capability-evidence/claude.d.ts +37 -0
  16. package/dist/app/capability-evidence/claude.js +206 -0
  17. package/dist/app/capability-evidence/codex.d.ts +35 -0
  18. package/dist/app/capability-evidence/codex.js +193 -0
  19. package/dist/app/capability-evidence/index.d.ts +33 -0
  20. package/dist/app/capability-evidence/index.js +117 -0
  21. package/dist/app/capability-evidence/pi.d.ts +73 -0
  22. package/dist/app/capability-evidence/pi.js +669 -0
  23. package/dist/app/capability-evidence/shared.d.ts +69 -0
  24. package/dist/app/capability-evidence/shared.js +195 -0
  25. package/dist/app/collab-auto-handoff.d.ts +55 -0
  26. package/dist/app/collab-auto-handoff.js +384 -0
  27. package/dist/app/collab-bindings.d.ts +57 -0
  28. package/dist/app/collab-bindings.js +1006 -0
  29. package/dist/app/collab-bootstrap-transport.d.ts +163 -0
  30. package/dist/app/collab-bootstrap-transport.js +769 -0
  31. package/dist/app/collab-bootstrap.d.ts +137 -0
  32. package/dist/app/collab-bootstrap.js +3455 -0
  33. package/dist/app/collab-handoff.d.ts +12 -0
  34. package/dist/app/collab-handoff.js +511 -0
  35. package/dist/app/collab-messages.d.ts +2 -0
  36. package/dist/app/collab-messages.js +2 -0
  37. package/dist/app/collab-notify.d.ts +156 -0
  38. package/dist/app/collab-notify.js +1208 -0
  39. package/dist/app/collab-preflight.d.ts +34 -0
  40. package/dist/app/collab-preflight.js +96 -0
  41. package/dist/app/collab-recovery-executor.d.ts +161 -0
  42. package/dist/app/collab-recovery-executor.js +1153 -0
  43. package/dist/app/collab-recovery-projection.d.ts +103 -0
  44. package/dist/app/collab-recovery-projection.js +659 -0
  45. package/dist/app/collab-recovery.d.ts +25 -0
  46. package/dist/app/collab-recovery.js +429 -0
  47. package/dist/app/collab-resources.d.ts +16 -0
  48. package/dist/app/collab-resources.js +278 -0
  49. package/dist/app/collab-role-events.d.ts +22 -0
  50. package/dist/app/collab-role-events.js +329 -0
  51. package/dist/app/collab-role-orchestration.d.ts +53 -0
  52. package/dist/app/collab-role-orchestration.js +601 -0
  53. package/dist/app/collab-role-plan.d.ts +81 -0
  54. package/dist/app/collab-role-plan.js +504 -0
  55. package/dist/app/collab-role-repair.d.ts +109 -0
  56. package/dist/app/collab-role-repair.js +821 -0
  57. package/dist/app/collab-rollover.d.ts +263 -0
  58. package/dist/app/collab-rollover.js +1112 -0
  59. package/dist/app/collab-route.d.ts +64 -0
  60. package/dist/app/collab-route.js +240 -0
  61. package/dist/app/collab-session-transition.d.ts +221 -0
  62. package/dist/app/collab-session-transition.js +3334 -0
  63. package/dist/app/collab-state.d.ts +7 -0
  64. package/dist/app/collab-state.js +162 -0
  65. package/dist/app/collab-task-store.d.ts +22 -0
  66. package/dist/app/collab-task-store.js +204 -0
  67. package/dist/app/collab-work-execution.d.ts +127 -0
  68. package/dist/app/collab-work-execution.js +1027 -0
  69. package/dist/app/collab-work-plan.d.ts +87 -0
  70. package/dist/app/collab-work-plan.js +437 -0
  71. package/dist/app/collaboration.d.ts +8 -2
  72. package/dist/app/collaboration.js +101 -12
  73. package/dist/app/doctor.js +108 -5
  74. package/dist/app/host-mcp.js +34 -8
  75. package/dist/app/host-probe.js +4 -4
  76. package/dist/app/init.d.ts +58 -4
  77. package/dist/app/init.js +768 -155
  78. package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
  79. package/dist/app/managed-pi-bootstrap-admission.js +104 -0
  80. package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
  81. package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
  82. package/dist/app/managed-pi-delivery.d.ts +8 -0
  83. package/dist/app/managed-pi-delivery.js +110 -0
  84. package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
  85. package/dist/app/managed-pi-handoff-receiver.js +263 -0
  86. package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
  87. package/dist/app/managed-pi-handoff-reference.js +60 -0
  88. package/dist/app/managed-pi-herdr.d.ts +21 -0
  89. package/dist/app/managed-pi-herdr.js +185 -0
  90. package/dist/app/managed-pi-initial-admission.d.ts +28 -0
  91. package/dist/app/managed-pi-initial-admission.js +416 -0
  92. package/dist/app/managed-pi-initial-launch.d.ts +5 -0
  93. package/dist/app/managed-pi-initial-launch.js +61 -0
  94. package/dist/app/managed-pi-native-observation.d.ts +36 -0
  95. package/dist/app/managed-pi-native-observation.js +191 -0
  96. package/dist/app/managed-pi-readiness.d.ts +29 -0
  97. package/dist/app/managed-pi-readiness.js +288 -0
  98. package/dist/app/managed-pi-repair-stop.d.ts +21 -0
  99. package/dist/app/managed-pi-repair-stop.js +371 -0
  100. package/dist/app/managed-pi-resume-admission.d.ts +32 -0
  101. package/dist/app/managed-pi-resume-admission.js +618 -0
  102. package/dist/app/managed-pi-retirement.d.ts +28 -0
  103. package/dist/app/managed-pi-retirement.js +112 -0
  104. package/dist/app/managed-pi-sdk-control.d.ts +7 -0
  105. package/dist/app/managed-pi-sdk-control.js +169 -0
  106. package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
  107. package/dist/app/managed-pi-sdk-runtime.js +483 -0
  108. package/dist/app/managed-pi-source-admission.d.ts +14 -0
  109. package/dist/app/managed-pi-source-admission.js +323 -0
  110. package/dist/app/managed-pi-transition-host.d.ts +36 -0
  111. package/dist/app/managed-pi-transition-host.js +292 -0
  112. package/dist/app/managed-pi.d.ts +54 -0
  113. package/dist/app/managed-pi.js +248 -0
  114. package/dist/app/shell-readiness.d.ts +66 -0
  115. package/dist/app/shell-readiness.js +368 -0
  116. package/dist/app/task-pin.d.ts +2 -23
  117. package/dist/app/task-pin.js +2 -113
  118. package/dist/cli/commands/collaboration-binding.d.ts +13 -0
  119. package/dist/cli/commands/collaboration-binding.js +118 -0
  120. package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
  121. package/dist/cli/commands/collaboration-bootstrap.js +84 -0
  122. package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
  123. package/dist/cli/commands/collaboration-capabilities.js +127 -0
  124. package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
  125. package/dist/cli/commands/collaboration-handoff.js +102 -0
  126. package/dist/cli/commands/collaboration-message.d.ts +17 -0
  127. package/dist/cli/commands/collaboration-message.js +177 -0
  128. package/dist/cli/commands/collaboration-plan.d.ts +8 -0
  129. package/dist/cli/commands/collaboration-plan.js +108 -0
  130. package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
  131. package/dist/cli/commands/collaboration-preflight.js +167 -0
  132. package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
  133. package/dist/cli/commands/collaboration-recovery-run.js +224 -0
  134. package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
  135. package/dist/cli/commands/collaboration-recovery.js +104 -0
  136. package/dist/cli/commands/collaboration-resources.d.ts +11 -0
  137. package/dist/cli/commands/collaboration-resources.js +96 -0
  138. package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
  139. package/dist/cli/commands/collaboration-role-plan.js +69 -0
  140. package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
  141. package/dist/cli/commands/collaboration-role-run.js +128 -0
  142. package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
  143. package/dist/cli/commands/collaboration-rollover.js +103 -0
  144. package/dist/cli/commands/collaboration-state.d.ts +10 -0
  145. package/dist/cli/commands/collaboration-state.js +96 -0
  146. package/dist/cli/commands/collaboration-transition.d.ts +10 -0
  147. package/dist/cli/commands/collaboration-transition.js +75 -0
  148. package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
  149. package/dist/cli/commands/collaboration-work-run.js +178 -0
  150. package/dist/cli/commands/collaboration.d.ts +4 -0
  151. package/dist/cli/commands/collaboration.js +39 -4
  152. package/dist/cli/commands/continuity.js +2 -6
  153. package/dist/cli/commands/docs.d.ts +2 -0
  154. package/dist/cli/commands/docs.js +89 -15
  155. package/dist/cli/commands/host.d.ts +7 -1
  156. package/dist/cli/commands/host.js +122 -7
  157. package/dist/cli/commands/init.d.ts +20 -3
  158. package/dist/cli/commands/init.js +897 -232
  159. package/dist/cli/commands/mcp.d.ts +1 -0
  160. package/dist/cli/commands/mcp.js +11 -5
  161. package/dist/cli/commands/repair.d.ts +12 -0
  162. package/dist/cli/commands/repair.js +41 -0
  163. package/dist/cli/commands/runtime.d.ts +1 -1
  164. package/dist/cli/commands/runtime.js +139 -38
  165. package/dist/cli/commands/session.d.ts +7 -0
  166. package/dist/cli/commands/session.js +82 -0
  167. package/dist/cli/commands/task.js +19 -5
  168. package/dist/cli/run.js +866 -24
  169. package/dist/code/identity.d.ts +7 -0
  170. package/dist/code/identity.js +7 -3
  171. package/dist/code/registration.js +7 -0
  172. package/dist/code/service.d.ts +9 -0
  173. package/dist/code/service.js +16 -3
  174. package/dist/code/text.js +19 -7
  175. package/dist/collab/admission.d.ts +91 -66
  176. package/dist/collab/admission.js +21 -6
  177. package/dist/collab/canonical.d.ts +1 -1
  178. package/dist/collab/canonical.js +44 -3
  179. package/dist/collab/envelope.d.ts +2 -2
  180. package/dist/collab/envelope.js +12 -12
  181. package/dist/collab/first-work.d.ts +3 -0
  182. package/dist/collab/first-work.js +15 -0
  183. package/dist/collab/handoff.d.ts +16 -0
  184. package/dist/collab/handoff.js +39 -5
  185. package/dist/collab/index.d.ts +97 -1
  186. package/dist/collab/index.js +9 -1
  187. package/dist/collab/messages.js +126 -3
  188. package/dist/collab/persisted-messages.d.ts +123 -0
  189. package/dist/collab/persisted-messages.js +496 -0
  190. package/dist/collab/preflight.d.ts +1 -1
  191. package/dist/collab/resources.d.ts +13 -3
  192. package/dist/collab/resources.js +37 -6
  193. package/dist/collab/rollover.d.ts +311 -0
  194. package/dist/collab/rollover.js +1024 -0
  195. package/dist/collab/store.d.ts +1 -0
  196. package/dist/collab/store.js +1 -4
  197. package/dist/collab/types.d.ts +2 -354
  198. package/dist/collab/validation.d.ts +2 -10
  199. package/dist/collab/validation.js +2 -303
  200. package/dist/collab/work-plan.d.ts +105 -0
  201. package/dist/collab/work-plan.js +843 -0
  202. package/dist/continuity/context.d.ts +11 -2
  203. package/dist/continuity/context.js +100 -13
  204. package/dist/continuity/handoff-bundle.d.ts +296 -0
  205. package/dist/continuity/handoff-bundle.js +477 -0
  206. package/dist/continuity/index.d.ts +2 -0
  207. package/dist/continuity/index.js +1 -0
  208. package/dist/continuity/paths.js +10 -2
  209. package/dist/continuity/service.js +91 -15
  210. package/dist/continuity/task-pin.d.ts +24 -0
  211. package/dist/continuity/task-pin.js +590 -0
  212. package/dist/doctor.d.ts +1 -0
  213. package/dist/doctor.js +6 -0
  214. package/dist/experimental/index.d.ts +13 -0
  215. package/dist/experimental/index.js +25 -0
  216. package/dist/experimental/pi.d.ts +4 -0
  217. package/dist/experimental/pi.js +12 -0
  218. package/dist/hosts/claude/native.js +1 -1
  219. package/dist/hosts/codex/native.js +1 -1
  220. package/dist/hosts/codex/protocol.js +1 -1
  221. package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
  222. package/dist/hosts/integration/bootstrap-config.js +254 -0
  223. package/dist/hosts/integration/bundle-export.d.ts +44 -0
  224. package/dist/hosts/integration/bundle-export.js +366 -3
  225. package/dist/hosts/integration/bundle.js +3 -0
  226. package/dist/hosts/integration/hook.js +36 -9
  227. package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
  228. package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
  229. package/dist/hosts/integration/pappra-hook.mjs +61 -24749
  230. package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
  231. package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
  232. package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
  233. package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
  234. package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
  235. package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
  236. package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
  237. package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
  238. package/dist/hosts/integration/pi-native-events.d.ts +112 -0
  239. package/dist/hosts/integration/pi-native-events.js +348 -0
  240. package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
  241. package/dist/hosts/integration/pi-rollover-extension.js +431 -0
  242. package/dist/hosts/integration/recovery-config.d.ts +48 -0
  243. package/dist/hosts/integration/recovery-config.js +117 -0
  244. package/dist/hosts/integration/service.d.ts +6 -1
  245. package/dist/hosts/integration/service.js +485 -35
  246. package/dist/hosts/integration/support.d.ts +7 -0
  247. package/dist/hosts/integration/support.js +9 -1
  248. package/dist/hosts/integration/templates.d.ts +3 -0
  249. package/dist/hosts/integration/templates.js +26 -3
  250. package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
  251. package/dist/hosts/pi/managed-host-identity.js +40 -0
  252. package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
  253. package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
  254. package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
  255. package/dist/hosts/pi/managed-sdk-session.js +1639 -0
  256. package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
  257. package/dist/hosts/pi/managed-session-extension.js +84 -0
  258. package/dist/hosts/pi/managed-session.d.ts +177 -0
  259. package/dist/hosts/pi/managed-session.js +1557 -0
  260. package/dist/hosts/pi/protocol.js +1 -1
  261. package/dist/hosts/pi/query-result.js +1 -1
  262. package/dist/hosts/pi/resume-capability.d.ts +41 -0
  263. package/dist/hosts/pi/resume-capability.js +33 -0
  264. package/dist/hosts/pi/rpc.d.ts +182 -134
  265. package/dist/hosts/pi/rpc.js +230 -37
  266. package/dist/hosts/pilot.d.ts +97 -74
  267. package/dist/hosts/pilot.js +5 -1
  268. package/dist/hosts/version.d.ts +3 -0
  269. package/dist/hosts/version.js +18 -0
  270. package/dist/index.d.ts +0 -1
  271. package/dist/index.js +0 -1
  272. package/dist/infra/bounded-file.d.ts +19 -0
  273. package/dist/infra/bounded-file.js +66 -0
  274. package/dist/infra/fs.js +10 -2
  275. package/dist/infra/git.d.ts +29 -0
  276. package/dist/infra/git.js +45 -9
  277. package/dist/infra/json-property.d.ts +2 -0
  278. package/dist/infra/json-property.js +69 -0
  279. package/dist/infra/json.d.ts +4 -0
  280. package/dist/infra/json.js +24 -0
  281. package/dist/infra/owned-process-group.js +1 -1
  282. package/dist/infra/private-lock.d.ts +16 -0
  283. package/dist/infra/private-lock.js +148 -0
  284. package/dist/infra/private-state.d.ts +7 -0
  285. package/dist/infra/private-state.js +146 -0
  286. package/dist/infra/process-identity.d.ts +12 -0
  287. package/dist/infra/process-identity.js +142 -0
  288. package/dist/infra/validation.d.ts +5 -0
  289. package/dist/infra/validation.js +15 -0
  290. package/dist/init.d.ts +1 -0
  291. package/dist/init.js +6 -0
  292. package/dist/install/apply.d.ts +9 -1
  293. package/dist/install/apply.js +176 -12
  294. package/dist/install/dependencies.d.ts +38 -0
  295. package/dist/install/dependencies.js +282 -0
  296. package/dist/install/ownership.js +30 -1
  297. package/dist/install/references.d.ts +31 -0
  298. package/dist/install/references.js +208 -0
  299. package/dist/install/repair.d.ts +37 -0
  300. package/dist/install/repair.js +798 -0
  301. package/dist/install/reset-local.d.ts +16 -0
  302. package/dist/install/reset-local.js +304 -0
  303. package/dist/install/rollback.d.ts +3 -0
  304. package/dist/install/rollback.js +9 -3
  305. package/dist/install/runtime.d.ts +10 -6
  306. package/dist/install/runtime.js +299 -59
  307. package/dist/install/source-fingerprint.d.ts +2 -0
  308. package/dist/install/source-fingerprint.js +103 -0
  309. package/dist/knowledge/catalog.d.ts +1 -0
  310. package/dist/knowledge/catalog.js +143 -10
  311. package/dist/knowledge/journal.js +17 -7
  312. package/dist/knowledge/paths.js +2 -1
  313. package/dist/knowledge/refs.js +4 -0
  314. package/dist/knowledge/register-batch.d.ts +4 -0
  315. package/dist/knowledge/register-batch.js +225 -0
  316. package/dist/knowledge/relocate.d.ts +5 -0
  317. package/dist/knowledge/relocate.js +92 -0
  318. package/dist/knowledge/scan.js +103 -47
  319. package/dist/knowledge/service.d.ts +3 -0
  320. package/dist/knowledge/service.js +33 -1
  321. package/dist/knowledge/write.d.ts +4 -1
  322. package/dist/knowledge/write.js +1 -1
  323. package/dist/mcp/server.d.ts +4 -2
  324. package/dist/mcp/server.js +87 -136
  325. package/dist/schemas/collaboration-state-validation.d.ts +6 -0
  326. package/dist/schemas/collaboration-state-validation.js +342 -0
  327. package/dist/schemas/collaboration-state.d.ts +436 -0
  328. package/dist/schemas/collaboration-state.js +1 -0
  329. package/dist/schemas/collaboration.d.ts +335 -0
  330. package/dist/schemas/collaboration.js +272 -0
  331. package/dist/schemas/continuity.d.ts +8 -0
  332. package/dist/schemas/continuity.js +13 -3
  333. package/dist/schemas/doctor.d.ts +1 -0
  334. package/dist/schemas/doctor.js +1 -0
  335. package/dist/schemas/document.d.ts +61 -0
  336. package/dist/schemas/document.js +9 -0
  337. package/dist/schemas/errors.d.ts +1 -0
  338. package/dist/schemas/errors.js +1 -0
  339. package/dist/schemas/host.d.ts +24 -0
  340. package/dist/schemas/host.js +19 -0
  341. package/dist/schemas/install.d.ts +8 -2
  342. package/dist/schemas/install.js +11 -1
  343. package/dist/schemas/managed-pi-execution.d.ts +16 -0
  344. package/dist/schemas/managed-pi-execution.js +2 -0
  345. package/dist/schemas/outcome.d.ts +9 -5
  346. package/dist/workspace/config-file.js +1 -3
  347. package/dist/workspace/documents.js +12 -2
  348. package/dist/workspace/repositories.d.ts +7 -0
  349. package/dist/workspace/repositories.js +14 -5
  350. package/package.json +35 -35
  351. package/skills/pappra/SKILL.md +28 -7
  352. package/skills/pappra/references/collaboration.md +633 -0
  353. package/skills/pappra/references/environment.md +360 -0
  354. package/skills/pappra/references/hosts.md +20 -0
  355. package/skills/pappra/references/operations.md +36 -5
  356. package/dist/app/collab-from.d.ts +0 -35
  357. package/dist/app/collab-from.js +0 -84
@@ -1,9 +1,12 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { dirname, join, resolve } from 'node:path';
1
+ import { lstat, mkdir, mkdtemp, readFile, realpath, rename, rm, rmdir, writeFile, } from 'node:fs/promises';
2
+ import { basename, isAbsolute, dirname, join, resolve } from 'node:path';
3
3
  import { getPackageRoot } from '../../package-info.js';
4
4
  import { isPathInside } from '../../schemas/paths.js';
5
5
  import { loadHostBundle } from './bundle.js';
6
6
  import { renderHookConfigPatch } from './templates.js';
7
+ import { readBoundedFile } from '../../infra/bounded-file.js';
8
+ import { PI_RECOVERY_MAX_CONFIG_BYTES, readPiRecoveryConfig, } from './recovery-config.js';
9
+ const BOOTSTRAP_EXTENSION_DIR = 'extensions/pappra-bootstrap';
7
10
  function pluginHookExpression(host) {
8
11
  return host === 'claude'
9
12
  ? '${CLAUDE_PLUGIN_ROOT}/hooks/pappra-hook.mjs'
@@ -38,6 +41,178 @@ function hostManifest(host, version) {
38
41
  },
39
42
  };
40
43
  }
44
+ function bootstrapManifest(version) {
45
+ return {
46
+ name: '@phyzess/pappra-pi-bootstrap',
47
+ version,
48
+ description: 'Pappra Pi bootstrap package: opt-in successor gate with a private local task binding.',
49
+ type: 'module',
50
+ pi: {
51
+ extensions: [`./${BOOTSTRAP_EXTENSION_DIR}/index.ts`],
52
+ skills: ['./skills'],
53
+ },
54
+ peerDependencies: { '@earendil-works/pi-coding-agent': '*' },
55
+ };
56
+ }
57
+ function bootstrapEntry() {
58
+ return Buffer.from(`/**
59
+ * Generated Pappra Pi bootstrap extension. It registers the accepted default-deny
60
+ * successor gate from a private relative configuration; it installs, trusts, launches
61
+ * and dispatches nothing by itself.
62
+ */
63
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
64
+ import bootstrapExtension from "./runtime.mjs";
65
+
66
+ export default function (pi: ExtensionAPI): void {
67
+ bootstrapExtension(pi);
68
+ }
69
+ `, 'utf8');
70
+ }
71
+ /**
72
+ * Reserves a private sibling staging directory next to the requested destination. Nothing is
73
+ * written through the final pathname: the payload is assembled here and published after an
74
+ * exclusive final claim, so a later substitution of the destination cannot redirect a write.
75
+ */
76
+ async function beginBootstrapOutput(outDir) {
77
+ const resolved = resolve(outDir);
78
+ const finalName = basename(resolved);
79
+ if (finalName === '' || finalName === '.' || finalName === '..')
80
+ throw new Error('bootstrap bundle destination must be a directory path');
81
+ const parent = dirname(resolved);
82
+ let parentStat;
83
+ try {
84
+ parentStat = await lstat(parent);
85
+ }
86
+ catch {
87
+ throw new Error(`bootstrap bundle parent does not exist: ${parent}`);
88
+ }
89
+ if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
90
+ throw new Error('bootstrap bundle parent must be a real directory');
91
+ const realParent = await realpath(parent);
92
+ const finalPath = join(realParent, finalName);
93
+ let existing = null;
94
+ try {
95
+ existing = await lstat(finalPath);
96
+ }
97
+ catch (error) {
98
+ if (error.code !== 'ENOENT')
99
+ throw error;
100
+ }
101
+ if (existing !== null)
102
+ throw new Error('bootstrap bundle destination must not exist; pass a new private directory');
103
+ const stagePath = await mkdtemp(join(realParent, '.pappra-bootstrap-'));
104
+ const staged = await lstat(stagePath);
105
+ const stageReal = await realpath(stagePath);
106
+ return { finalPath, stagePath, stageReal, dev: staged.dev, ino: staged.ino };
107
+ }
108
+ const STAGE_DRIFT = 'bootstrap staging namespace changed during export';
109
+ /**
110
+ * Fails closed on observed staging namespace drift (symlink, type change or device/inode
111
+ * change). This models a check/use boundary, not a general same-user filesystem fence: portable
112
+ * Node exposes no anchored directory writes.
113
+ */
114
+ async function stageDrift(stage) {
115
+ try {
116
+ const current = await lstat(stage.stagePath);
117
+ if (current.isSymbolicLink() ||
118
+ !current.isDirectory() ||
119
+ current.dev !== stage.dev ||
120
+ current.ino !== stage.ino)
121
+ return STAGE_DRIFT;
122
+ const real = await realpath(stage.stagePath);
123
+ if (real !== stage.stageReal)
124
+ return STAGE_DRIFT;
125
+ return null;
126
+ }
127
+ catch {
128
+ return STAGE_DRIFT;
129
+ }
130
+ }
131
+ async function assertStage(stage) {
132
+ const drift = await stageDrift(stage);
133
+ if (drift !== null)
134
+ throw new Error(drift);
135
+ }
136
+ /** Removes only the staging directory this call created; substituted paths are retained. */
137
+ async function discardStage(stage) {
138
+ try {
139
+ const current = await lstat(stage.stagePath);
140
+ if (current.isSymbolicLink() ||
141
+ current.dev !== stage.dev ||
142
+ current.ino !== stage.ino)
143
+ return;
144
+ await rm(stage.stagePath, { recursive: true, force: false });
145
+ }
146
+ catch {
147
+ // Missing or unreadable after a substitution: keep whatever is there.
148
+ }
149
+ }
150
+ /**
151
+ * Claims the final path with an exclusive `mkdir`, which is the only portable no-replace
152
+ * directory primitive. Anything already at the path (empty directory, file or symlink) makes
153
+ * this fail and is preserved.
154
+ */
155
+ async function claimFinal(stage) {
156
+ await mkdir(stage.finalPath, { recursive: false, mode: 0o700 });
157
+ const claimed = await lstat(stage.finalPath);
158
+ return { dev: claimed.dev, ino: claimed.ino };
159
+ }
160
+ async function verifyClaim(stage, claim) {
161
+ const current = await lstat(stage.finalPath);
162
+ if (current.isSymbolicLink() ||
163
+ !current.isDirectory() ||
164
+ current.dev !== claim.dev ||
165
+ current.ino !== claim.ino)
166
+ throw new Error('bootstrap bundle destination changed during publication');
167
+ }
168
+ /** Removes the claim only when it is still the empty directory this call created. */
169
+ async function releaseClaim(stage, claim) {
170
+ try {
171
+ const current = await lstat(stage.finalPath);
172
+ if (current.isSymbolicLink() ||
173
+ !current.isDirectory() ||
174
+ current.dev !== claim.dev ||
175
+ current.ino !== claim.ino)
176
+ return;
177
+ // A directory claim must be removed with the directory operation: `rm` with
178
+ // `recursive: false` rejects a directory (EISDIR) even when it is empty.
179
+ await rmdir(stage.finalPath);
180
+ }
181
+ catch {
182
+ // A substituted or non-empty destination is preserved.
183
+ }
184
+ }
185
+ /**
186
+ * Publishes the completed staged directory over the empty claim created by this call. The
187
+ * claim identity and the stage identity are re-checked first; the staged inode is verified
188
+ * after publication. A same-user process racing exactly between these checks is outside what
189
+ * portable Node can fence (no renameat2/RENAME_NOREPLACE or openat), and is documented as a
190
+ * check/use limit rather than claimed as prevented.
191
+ */
192
+ async function publishStage(stage, claim) {
193
+ await verifyClaim(stage, claim);
194
+ await assertStage(stage);
195
+ await rename(stage.stagePath, stage.finalPath);
196
+ const final = await lstat(stage.finalPath);
197
+ if (final.isSymbolicLink() ||
198
+ !final.isDirectory() ||
199
+ final.dev !== stage.dev ||
200
+ final.ino !== stage.ino)
201
+ throw new Error('bootstrap publication did not produce the staged directory');
202
+ }
203
+ function piExtensionEntry(runtimeFile) {
204
+ return Buffer.from(`/** Generated opt-in Pi extension shim; it never installs or trusts itself. */\nimport type { ExtensionAPI } from "@earendil-works/pi-coding-agent";\nimport extension from "./${runtimeFile}";\n\nexport default function (pi: ExtensionAPI): void {\n extension(pi);\n}\n`, 'utf8');
205
+ }
206
+ function piExtensionManifest(version, extensions) {
207
+ return {
208
+ name: '@phyzess/pappra-pi-collaboration',
209
+ version,
210
+ description: 'Opt-in Pappra Pi collaboration extensions.',
211
+ type: 'module',
212
+ pi: { extensions: [...extensions], skills: ['./skills'] },
213
+ peerDependencies: { '@earendil-works/pi-coding-agent': '*' },
214
+ };
215
+ }
41
216
  /**
42
217
  * Writes a host-native plugin/package directory. This is an artifact export: it
43
218
  * does not register the plugin with a host, and it never claims the host will
@@ -48,13 +223,201 @@ export async function exportHostPluginBundle(options) {
48
223
  if (options.marketplace === true && options.host === 'pi') {
49
224
  throw new Error('Pi installs a directory directly; a marketplace wrapper is not supported.');
50
225
  }
226
+ if (options.bootstrap !== undefined && options.host !== 'pi')
227
+ throw new Error('bootstrap bundles are supported for the Pi host only');
228
+ if (options.bootstrap !== undefined && options.marketplace === true)
229
+ throw new Error('bootstrap bundles cannot be wrapped in a marketplace');
230
+ if ((options.recovery !== undefined || options.transition !== undefined) &&
231
+ options.host !== 'pi')
232
+ throw new Error('recovery and transition bundles are supported for the Pi host only');
233
+ if (options.bootstrap !== undefined &&
234
+ (options.recovery !== undefined || options.transition !== undefined))
235
+ throw new Error('bootstrap bundles cannot be combined with recovery or transition extensions');
236
+ if ((options.recovery !== undefined || options.transition !== undefined) &&
237
+ options.marketplace === true)
238
+ throw new Error('recovery and transition bundles cannot be wrapped in a marketplace');
51
239
  const pluginPrefix = options.marketplace === true ? 'pappra/' : '';
52
240
  const pluginRoot = options.marketplace === true ? join(outRoot, 'pappra') : outRoot;
241
+ const hookSource = join(getPackageRoot(), 'dist/hosts/integration/pappra-hook.mjs');
53
242
  const payload = await loadHostBundle(options.host, options.packageVersion, {
54
- hookSource: join(getPackageRoot(), 'dist/hosts/integration/pappra-hook.mjs'),
243
+ hookSource,
55
244
  hookExpression: pluginHookExpression(options.host),
56
245
  });
57
246
  const written = [];
247
+ if (options.bootstrap !== undefined) {
248
+ const bootstrap = options.bootstrap;
249
+ let artifact;
250
+ try {
251
+ artifact = await readFile(bootstrap.artifactPath);
252
+ }
253
+ catch {
254
+ throw new Error(`bootstrap extension artifact is missing: ${bootstrap.artifactPath}; build the package first`);
255
+ }
256
+ if (options.marketplace === true)
257
+ throw new Error('bootstrap bundles cannot be wrapped in a marketplace');
258
+ const staged = await beginBootstrapOutput(options.outDir);
259
+ let claim = null;
260
+ try {
261
+ const writeEntry = async (path, bytes, mode) => {
262
+ const target = join(staged.stagePath, ...path.split('/'));
263
+ if (!isPathInside(staged.stagePath, target))
264
+ throw new Error(`Unsafe plugin bundle path: ${path}`);
265
+ await assertStage(staged);
266
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
267
+ await assertStage(staged);
268
+ await writeFile(target, bytes, mode === undefined ? undefined : { mode });
269
+ await assertStage(staged);
270
+ written.push(path);
271
+ };
272
+ for (const [path, bytes] of payload.contents) {
273
+ if (!path.startsWith('skills/'))
274
+ continue;
275
+ await writeEntry(path, bytes);
276
+ }
277
+ await writeEntry(`${BOOTSTRAP_EXTENSION_DIR}/index.ts`, bootstrapEntry());
278
+ await writeEntry(`${BOOTSTRAP_EXTENSION_DIR}/runtime.mjs`, artifact);
279
+ await writeEntry('package.json', Buffer.from(`${JSON.stringify(bootstrapManifest(options.packageVersion), null, 2)}\n`));
280
+ // The private binding is written last so a staging substitution is detected before it.
281
+ await writeEntry(`${BOOTSTRAP_EXTENSION_DIR}/bootstrap.json`, bootstrap.configBytes, 0o600);
282
+ claim = await claimFinal(staged);
283
+ await publishStage(staged, claim);
284
+ }
285
+ catch (error) {
286
+ await discardStage(staged);
287
+ if (claim !== null)
288
+ await releaseClaim(staged, claim);
289
+ throw error;
290
+ }
291
+ return {
292
+ host: options.host,
293
+ outDir: staged.finalPath,
294
+ files: written.sort((left, right) => left.localeCompare(right)),
295
+ pluginManifest: 'package.json',
296
+ hookConfig: null,
297
+ registration: [
298
+ 'Install the directory as a Pi package only after reviewing the private binding; Pappra does not install or trust it.',
299
+ 'Loading is not enforced gating; check the read-only bootstrap status before any model work.',
300
+ ],
301
+ verification: 'unsupported',
302
+ bootstrap: {
303
+ schema: bootstrap.config.schema,
304
+ configDigest: bootstrap.configDigest,
305
+ mode: 'deny-until-completed-transfer',
306
+ registeredByExport: false,
307
+ },
308
+ };
309
+ }
310
+ if (options.recovery !== undefined || options.transition !== undefined) {
311
+ let recoveryBytes = null;
312
+ let recoverySchema = null;
313
+ let recoveryDigest = null;
314
+ if (options.recovery !== undefined) {
315
+ if (!isAbsolute(options.recovery.configFile))
316
+ throw new Error('recovery config path must be absolute');
317
+ const validated = readPiRecoveryConfig(options.recovery.configFile);
318
+ if (!validated.ok)
319
+ throw new Error(`recovery config is invalid (${validated.code})`);
320
+ const pinned = readBoundedFile(options.recovery.configFile, PI_RECOVERY_MAX_CONFIG_BYTES);
321
+ if (!pinned.ok || pinned.digest !== validated.digest)
322
+ throw new Error('recovery config changed or became unreadable during validation');
323
+ recoveryBytes = pinned.bytes;
324
+ recoverySchema = validated.config.schema;
325
+ recoveryDigest = validated.digest;
326
+ }
327
+ let recoveryArtifact = null;
328
+ let transitionArtifact = null;
329
+ try {
330
+ if (options.recovery !== undefined)
331
+ recoveryArtifact = await readFile(options.recovery.artifactPath);
332
+ if (options.transition !== undefined)
333
+ transitionArtifact = await readFile(options.transition.artifactPath);
334
+ }
335
+ catch {
336
+ throw new Error('an opt-in Pi extension artifact is missing; build the package before exporting');
337
+ }
338
+ const staged = await beginBootstrapOutput(options.outDir);
339
+ let claim = null;
340
+ const extensionPaths = [];
341
+ try {
342
+ const writeEntry = async (path, bytes, mode) => {
343
+ const target = join(staged.stagePath, ...path.split('/'));
344
+ if (!isPathInside(staged.stagePath, target))
345
+ throw new Error(`Unsafe plugin bundle path: ${path}`);
346
+ await assertStage(staged);
347
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
348
+ await assertStage(staged);
349
+ await writeFile(target, bytes, mode === undefined ? undefined : { mode });
350
+ await assertStage(staged);
351
+ written.push(path);
352
+ };
353
+ for (const [path, bytes] of payload.contents) {
354
+ if (path.startsWith('skills/'))
355
+ await writeEntry(path, bytes);
356
+ }
357
+ if (recoveryArtifact !== null) {
358
+ const directory = 'extensions/pappra-recovery';
359
+ await writeEntry(`${directory}/index.ts`, piExtensionEntry('runtime.mjs'));
360
+ await writeEntry(`${directory}/runtime.mjs`, recoveryArtifact);
361
+ extensionPaths.push(`./${directory}/index.ts`);
362
+ }
363
+ if (transitionArtifact !== null) {
364
+ const directory = 'extensions/pappra-session-transition';
365
+ await writeEntry(`${directory}/index.ts`, piExtensionEntry('runtime.mjs'));
366
+ await writeEntry(`${directory}/runtime.mjs`, transitionArtifact);
367
+ extensionPaths.push(`./${directory}/index.ts`);
368
+ }
369
+ await writeEntry('package.json', Buffer.from(`${JSON.stringify(piExtensionManifest(options.packageVersion, extensionPaths), null, 2)}\n`));
370
+ if (recoveryBytes !== null)
371
+ await writeEntry('extensions/pappra-recovery/recovery.json', recoveryBytes, 0o600);
372
+ claim = await claimFinal(staged);
373
+ await publishStage(staged, claim);
374
+ }
375
+ catch (error) {
376
+ await discardStage(staged);
377
+ if (claim !== null)
378
+ await releaseClaim(staged, claim);
379
+ throw error;
380
+ }
381
+ const registration = [
382
+ 'Review and install this opt-in Pi package explicitly; export does not register, trust, enable or launch it.',
383
+ ...(recoveryArtifact === null
384
+ ? []
385
+ : [
386
+ 'Recovery requires the explicitly selected private task config and only runs on supported observed startup/settled/manual triggers; no reconnect event or generic outage recovery is claimed.',
387
+ ]),
388
+ ...(transitionArtifact === null
389
+ ? []
390
+ : [
391
+ 'The transition command remains blocked without native quiescence proof; task retirement is not native session archive or automatic takeover.',
392
+ ]),
393
+ ];
394
+ return {
395
+ host: 'pi',
396
+ outDir: staged.finalPath,
397
+ files: written.sort((left, right) => left.localeCompare(right)),
398
+ pluginManifest: 'package.json',
399
+ hookConfig: null,
400
+ registration,
401
+ verification: 'unsupported',
402
+ ...(recoverySchema === null || recoveryDigest === null
403
+ ? {}
404
+ : {
405
+ recovery: {
406
+ schema: recoverySchema,
407
+ configDigest: recoveryDigest,
408
+ registeredByExport: false,
409
+ },
410
+ }),
411
+ ...(transitionArtifact === null
412
+ ? {}
413
+ : {
414
+ transition: {
415
+ registeredByExport: false,
416
+ nativeQuiescence: 'unavailable',
417
+ },
418
+ }),
419
+ };
420
+ }
58
421
  const writeEntry = async (path, bytes) => {
59
422
  const target = join(outRoot, ...path.split('/'));
60
423
  if (!isPathInside(outRoot, target)) {
@@ -27,6 +27,9 @@ export function installPathForBundleFile(host, sourcePath) {
27
27
  if (host === 'pi' && sourcePath === 'templates/pi/entry.ts') {
28
28
  return 'extensions/pappra/index.ts';
29
29
  }
30
+ if (host === 'pi' && sourcePath === 'templates/pi/runtime.mjs') {
31
+ return 'extensions/pappra/runtime.mjs';
32
+ }
30
33
  return sourcePath;
31
34
  }
32
35
  /**
@@ -6,11 +6,12 @@ import { diagnosticCodes } from '../../schemas/errors.js';
6
6
  import { HOST_HOOK_INPUT_SCHEMA, commandHookEventSchema, hostHookInputSchema, hostHookResultSchema, } from '../../schemas/host.js';
7
7
  import { observePath, readFileBounded, resolveRealpath, sha256Digest, } from '../../infra/fs.js';
8
8
  import { isPathInside } from '../../schemas/paths.js';
9
+ import { ensurePrivateState } from '../../infra/private-state.js';
9
10
  import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
10
11
  import { inspectMachineConfig, loadWorkspaceConfig, } from '../../workspace/config.js';
11
12
  import { CheckpointService } from '../../continuity/service.js';
12
13
  import { ensureStatePathBoundary } from '../../install/ownership.js';
13
- import { readAutoResume, readTaskPin } from '../../app/task-pin.js';
14
+ import { readAutoResume, readTaskPin } from '../../continuity/task-pin.js';
14
15
  import { nativeEventFor } from './support.js';
15
16
  const DEFAULT_MAX_INPUT_BYTES = 1024 * 1024;
16
17
  const DEFAULT_DEADLINE_MS = 1000;
@@ -40,6 +41,9 @@ function result(input) {
40
41
  export function renderHostHookOutput(host, result) {
41
42
  if (result.context === null || result.event === null)
42
43
  return null;
44
+ // PostCompact is an observation hook, not SessionStart context injection.
45
+ if (host === 'claude' && result.event === 'compact')
46
+ return null;
43
47
  const context = result.context.length > 8000
44
48
  ? `${result.context.slice(0, 7997)}...`
45
49
  : result.context;
@@ -84,9 +88,9 @@ async function privateSegmentsSafe(root, directory) {
84
88
  }
85
89
  return true;
86
90
  }
87
- async function boundedAdvisory(root, event, taskId, workspaceName) {
91
+ async function boundedAdvisory(root, event, taskId, workspaceName, workspaceId) {
88
92
  const lines = [
89
- `Pappra workspace "${workspaceName}" is registered for lifecycle event ${event}.`,
93
+ `Pappra workspace ${JSON.stringify(workspaceName)} is registered for lifecycle event ${event}.`,
90
94
  ];
91
95
  if (taskId === null) {
92
96
  lines.push('No explicit task was selected; run `pappra checkpoint --task <id>` or `pappra resume <id>` for manual continuity.');
@@ -97,14 +101,18 @@ async function boundedAdvisory(root, event, taskId, workspaceName) {
97
101
  taskId,
98
102
  currentSession: null,
99
103
  });
100
- if (!resumed.ok || resumed.packet === null) {
104
+ if (!resumed.ok ||
105
+ resumed.packet === null ||
106
+ resumed.packet.workspaceId !== workspaceId ||
107
+ resumed.packet.taskId !== taskId ||
108
+ resumed.packet.identityChecks.some((check) => check.status !== 'ok')) {
101
109
  lines.push(`Checkpoint ${taskId} is unavailable or incomplete; manual pappra checkpoint/resume is required.`);
102
110
  }
103
111
  else {
104
112
  lines.push(`Checkpoint ${taskId} is available for manual review.`);
105
- lines.push(`Objective: ${resumed.packet.boundedContext.objective}`);
113
+ lines.push(`Advisory objective: ${JSON.stringify(resumed.packet.boundedContext.objective)}`);
106
114
  if (resumed.packet.boundedContext.nextActions.length > 0) {
107
- lines.push(`Next actions: ${resumed.packet.boundedContext.nextActions.join('; ')}`);
115
+ lines.push(`Advisory next actions: ${JSON.stringify(resumed.packet.boundedContext.nextActions)}`);
108
116
  }
109
117
  if (!resumed.packet.executionAllowed) {
110
118
  lines.push('Context does not authorize execution; inspect blockers before acting.');
@@ -218,9 +226,12 @@ export function mapNativeHookEvent(host, raw, _fallbackEvent, _fallbackCwd) {
218
226
  if (native === 'SessionStart') {
219
227
  const source = parsed.data.source;
220
228
  event =
221
- source === 'resume'
229
+ source === 'resume' || source === 'compact'
222
230
  ? 'resume'
223
- : source === undefined || source === 'startup' || source === 'new'
231
+ : source === undefined ||
232
+ source === 'startup' ||
233
+ source === 'new' ||
234
+ source === 'clear'
224
235
  ? 'startup'
225
236
  : null;
226
237
  }
@@ -461,6 +472,22 @@ export async function runHostHook(input, options = {}) {
461
472
  ],
462
473
  });
463
474
  }
475
+ const privateState = await ensurePrivateState(root);
476
+ if (!privateState.ok) {
477
+ return result({
478
+ ok: false,
479
+ status: 'failed',
480
+ host: value.host,
481
+ event: value.event,
482
+ eventKey,
483
+ duplicate: false,
484
+ context: null,
485
+ diagnostics: [
486
+ diagnostic(privateState.code, privateState.message, root, privateState.nextAction ??
487
+ 'Protect private workspace state before recording the host event.'),
488
+ ],
489
+ });
490
+ }
464
491
  await mkdir(directory, { recursive: true, mode: 0o700 });
465
492
  const directoryAfter = await observePath(directory);
466
493
  const realDirectory = await resolveRealpath(directory);
@@ -492,7 +519,7 @@ export async function runHostHook(input, options = {}) {
492
519
  };
493
520
  const pinnedTask = await readTaskPin(root);
494
521
  const autoResume = pinnedTask !== null && (await readAutoResume(root));
495
- const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name);
522
+ const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name, workspaceRead.value.workspaceId);
496
523
  if (clock() - started > deadline) {
497
524
  return result({
498
525
  ok: false,