sliccy 6.226.0 → 6.226.2

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 (173) hide show
  1. package/dist/node-server/sudo/dialog-backends.d.ts +10 -5
  2. package/dist/node-server/sudo/dialog-backends.js +47 -10
  3. package/dist/node-server/sudo/electron-backend.d.ts +4 -3
  4. package/dist/node-server/sudo/electron-backend.js +6 -3
  5. package/dist/node-server/sudo/endpoint.d.ts +4 -3
  6. package/dist/node-server/sudo/endpoint.js +6 -4
  7. package/dist/node-server/sudo/select.d.ts +1 -1
  8. package/dist/node-server/sudo/select.js +1 -1
  9. package/dist/node-server/sudo/tty-backend.d.ts +4 -2
  10. package/dist/node-server/sudo/tty-backend.js +5 -2
  11. package/dist/node-server/sudo/types.d.ts +29 -1
  12. package/dist/node-server/sudo/types.js +8 -1
  13. package/dist/ui/.vite/manifest.json +182 -182
  14. package/dist/ui/assets/{SKILL-nuxIaHgr.js → SKILL-BKKGj_a4.js} +1 -1
  15. package/dist/ui/assets/{SKILL-CX_QtSBf.js → SKILL-DfQfFV0B.js} +1 -1
  16. package/dist/ui/assets/__vite-browser-external-ZetWQ5ax.js +1 -0
  17. package/dist/ui/assets/{account-store-Vz9-riGq.js → account-store-CckwuDB2.js} +1 -1
  18. package/dist/ui/assets/{account-store-DVZ4hauw.js → account-store-DP_9wI9F.js} +1 -1
  19. package/dist/ui/assets/{adobe-CF56hodW.js → adobe-BX6MOOVZ.js} +1 -1
  20. package/dist/ui/assets/{adobe-CYas8Psn.js → adobe-CpN64YaE.js} +1 -1
  21. package/dist/ui/assets/{agent-panels-Diwj4i8p.js → agent-panels-CGXjqFbQ.js} +1 -1
  22. package/dist/ui/assets/{almost-bash-shell-headless-CHgTUoCC.js → almost-bash-shell-headless-DANvPMM_.js} +36 -36
  23. package/dist/ui/assets/{apply-layout-BAg0pCFs.js → apply-layout-BqFOBUKs.js} +1 -1
  24. package/dist/ui/assets/{apps-fyzsGYCb.js → apps-C4Y2CDb2.js} +1 -1
  25. package/dist/ui/assets/{azure-openai-Bb-Ldhtu.js → azure-openai-Djld28cS.js} +1 -1
  26. package/dist/ui/assets/{azure-openai-C8IekNn6.js → azure-openai-Wvaw8FEs.js} +1 -1
  27. package/dist/ui/assets/{backend-hostfs-BqbUNe2p.js → backend-hostfs-B383ob12.js} +1 -1
  28. package/dist/ui/assets/{cdp-BAtyag0U.js → cdp-B_M-UmZ-.js} +1 -1
  29. package/dist/ui/assets/{cerebras-Dtah626N.js → cerebras-CYnqzV28.js} +1 -1
  30. package/dist/ui/assets/{cerebras-C6dm-Gdt.js → cerebras-CnYplQcE.js} +1 -1
  31. package/dist/ui/assets/{composer-speech-D-Ha5J9c.js → composer-speech-F0aNDREI.js} +1 -1
  32. package/dist/ui/assets/{connect-surface-Bm49ONML.js → connect-surface-BnTWpdsP.js} +1 -1
  33. package/dist/ui/assets/{directed-approval-CL6oC1eR.js → directed-approval-ClnG3YEs.js} +1 -1
  34. package/dist/ui/assets/{emscripten-vfs-hook-Ct2D2xOU.js → emscripten-vfs-hook-d03P2Jrb.js} +1 -1
  35. package/dist/ui/assets/{ensure-speech-assets-BifTQzDv.js → ensure-speech-assets-BAwQVUZ-.js} +1 -1
  36. package/dist/ui/assets/{frozen-archive-writer-ZdPRZWFn.js → frozen-archive-writer-DlQ2pWzw.js} +1 -1
  37. package/dist/ui/assets/fs-B6V0zxAV.js +2 -0
  38. package/dist/ui/assets/fs-DFXgS-c1.js +1 -0
  39. package/dist/ui/assets/{gelatiere-store-DmBDtPPr.js → gelatiere-store-aggur0yp.js} +1 -1
  40. package/dist/ui/assets/{gelatiere-unit-BKloopHu.js → gelatiere-unit-is8dhW-d.js} +1 -1
  41. package/dist/ui/assets/{github-MyaD9kA_.js → github-CWBn99Bd.js} +2 -2
  42. package/dist/ui/assets/{github-auth-V7QfsMAL.js → github-auth-yNHDwebn.js} +1 -1
  43. package/dist/ui/assets/{github-copilot-ByA9KPsX.js → github-copilot-BADuy5GH.js} +1 -1
  44. package/dist/ui/assets/{github-copilot-Cpv94oc0.js → github-copilot-BOrHa5RN.js} +1 -1
  45. package/dist/ui/assets/{github-install-D4KsbDhD.js → github-install-BIoofnjZ.js} +1 -1
  46. package/dist/ui/assets/{github-fGqZBI0M.js → github-pVY7MyJd.js} +1 -1
  47. package/dist/ui/assets/{github-zip-lkg2X0sW.js → github-zip-TmRPTM75.js} +1 -1
  48. package/dist/ui/assets/{go-driver-mph4-dd3.js → go-driver-CYR67gv1.js} +1 -1
  49. package/dist/ui/assets/{hear-CKT9g9Z6.js → hear-JttBNQ7c.js} +1 -1
  50. package/dist/ui/assets/{help-Bpf-0fKM.js → help-DvETwJex.js} +1 -1
  51. package/dist/ui/assets/{installer-C9uPgxs5.js → installer-CmAHuSft.js} +1 -1
  52. package/dist/ui/assets/{ipk-mamba-5CJyn6wo.js → ipk-mamba-D1bLinsh.js} +1 -1
  53. package/dist/ui/assets/{js-realm-worker-C6-5D8d4.js → js-realm-worker-CsvflECa.js} +3 -3
  54. package/dist/ui/assets/{jsh-executor-Bj2IGcf8.js → jsh-executor-CEkiOk4d.js} +2 -2
  55. package/dist/ui/assets/{kernel-worker-D6Pl3EF9.js → kernel-worker-CSRynihq.js} +10 -10
  56. package/dist/ui/assets/{lick-ws-bridge-B_5drycq.js → lick-ws-bridge-C_07Sgiv.js} +1 -1
  57. package/dist/ui/assets/{lick-ws-bridge-D3K0eKle.js → lick-ws-bridge-ZBncAqka.js} +1 -1
  58. package/dist/ui/assets/{live-session-curation-CuGBo_Aq.js → live-session-curation-lunfYQqv.js} +1 -1
  59. package/dist/ui/assets/{live-session-snapshot-C6cSlLJB.js → live-session-snapshot-BMzJEYH6.js} +1 -1
  60. package/dist/ui/assets/{local-llm-DWzWQslN.js → local-llm-DYEdgnfp.js} +1 -1
  61. package/dist/ui/assets/{local-llm-Bj6Tfad8.js → local-llm-FyJMIUux.js} +1 -1
  62. package/dist/ui/assets/{main-cherry-BI2sg1_x.js → main-cherry-BknGKE1w.js} +1 -1
  63. package/dist/ui/assets/{main-BNVduXLq.js → main-mYxYChCJ.js} +3 -3
  64. package/dist/ui/assets/{memory-curation-seam-BT6vsnGt.js → memory-curation-seam-ff-huuyf.js} +1 -1
  65. package/dist/ui/assets/{memory-health-wNRNbxca.js → memory-health-hO_nQaAH.js} +1 -1
  66. package/dist/ui/assets/{memory-write-execute-CrntUhnI.js → memory-write-execute-CotJ6gZQ.js} +1 -1
  67. package/dist/ui/assets/{merge-folded-cost-into-frozen-BR9v1fOa.js → merge-folded-cost-into-frozen-85Z0pknu.js} +1 -1
  68. package/dist/ui/assets/{mount-D88b8AYY.js → mount-BXI8X7dl.js} +1 -1
  69. package/dist/ui/assets/{mount-DEMjVYLA.js → mount-dHmWGrgT.js} +3 -3
  70. package/dist/ui/assets/{new-session-BRXnm4uf.js → new-session-CwPpsslH.js} +2 -2
  71. package/dist/ui/assets/{oauth-bootstrap-6uukFP4J.js → oauth-bootstrap-DFH58dMK.js} +2 -2
  72. package/dist/ui/assets/{onboarding-orchestrator-Bnq2AMMH.js → onboarding-orchestrator-IM_eYwPO.js} +1 -1
  73. package/dist/ui/assets/{openai-codex-C42RJVvZ.js → openai-codex-B5TF7s7X.js} +1 -1
  74. package/dist/ui/assets/{openai-codex--LKVLvBd.js → openai-codex-DOLy2I4o.js} +1 -1
  75. package/dist/ui/assets/{openrouter-CJNDdzxq.js → openrouter-CQSD-mOw.js} +1 -1
  76. package/dist/ui/assets/{openrouter-KumQFnxo.js → openrouter-DQpeSY-A.js} +1 -1
  77. package/dist/ui/assets/{openrouter-free-InNdkaTv.js → openrouter-free-DB3Go5D-.js} +1 -1
  78. package/dist/ui/assets/{openrouter-free-D0nAH3BC.js → openrouter-free-cjPrMokY.js} +1 -1
  79. package/dist/ui/assets/{openrouter-oauth-DhSUuQMu.js → openrouter-oauth-B_Qr_P-V.js} +1 -1
  80. package/dist/ui/assets/{openrouter-oauth-9mgPOg_h.js → openrouter-oauth-DQeQAIy-.js} +1 -1
  81. package/dist/ui/assets/{opfs-mount-tM2pZiT4.js → opfs-mount-tnswXF8V.js} +1 -1
  82. package/dist/ui/assets/{panel-rpc-handlers-DvHt7N2U.js → panel-rpc-handlers-DWiTg8Ql.js} +2 -2
  83. package/dist/ui/assets/{panelize-shell-BUsm4WSn.js → panelize-shell-BKkiVZiC.js} +1 -1
  84. package/dist/ui/assets/{pi-edit-execution-C0WtBxXY.js → pi-edit-execution-DoPTCkJU.js} +1 -1
  85. package/dist/ui/assets/{process-worker-CoJic0og.js → process-worker-COJhrHY2.js} +1 -1
  86. package/dist/ui/assets/{provider-CZLApLhl.js → provider-DHEg3RIC.js} +2 -2
  87. package/dist/ui/assets/{provider-Bed4E1Pc.js → provider-Do8hIn0m.js} +1 -1
  88. package/dist/ui/assets/{provider-settings-ccNCduqT.js → provider-settings-x2iD9c0h.js} +1 -1
  89. package/dist/ui/assets/provider-store-access-4-s6k7tf.js +1 -0
  90. package/dist/ui/assets/provider-store-access-DXOTVVCa.js +1 -0
  91. package/dist/ui/assets/{providers-DYl9VQiG.js → providers-B8axP5LK.js} +2 -2
  92. package/dist/ui/assets/{providers-CvJLUl3u.js → providers-ChXzAurS.js} +1 -1
  93. package/dist/ui/assets/{py-realm-worker-BJhAN3Hi.js → py-realm-worker-D7ZPZyKh.js} +1 -1
  94. package/dist/ui/assets/{pyodide-BC3toCIx.js → pyodide-C89f2pno.js} +1 -1
  95. package/dist/ui/assets/{pyodide-65Z_RapY.js → pyodide-DdE1Qe05.js} +1 -1
  96. package/dist/ui/assets/{quick-llm-THt24v1I.js → quick-llm-DbTW7_qg.js} +1 -1
  97. package/dist/ui/assets/{realm-inprocess-BlymT55n.js → realm-inprocess-Ck6ydQwT.js} +3 -3
  98. package/dist/ui/assets/{realm-unhandled-rejections-BSOPtbcS.js → realm-unhandled-rejections-CeAkuLUL.js} +1 -1
  99. package/dist/ui/assets/{realm-unhandled-rejections-Pu1Sv2jS.js → realm-unhandled-rejections-CnU72aPe.js} +1 -1
  100. package/dist/ui/assets/{recovery-screen-wnxtIaWI.js → recovery-screen-ty-N4sk6.js} +1 -1
  101. package/dist/ui/assets/{remote-terminal-view--tkNTOqt.js → remote-terminal-view-CNpjHeSR.js} +2 -2
  102. package/dist/ui/assets/{require-guards-CdMtRPQA.js → require-guards-CMPvNDQg.js} +1 -1
  103. package/dist/ui/assets/{restore-rQpWBAp5.js → restore-CGbZluMQ.js} +1 -1
  104. package/dist/ui/assets/{run-DkCNZVlY.js → run-B9eYT4Oj.js} +2 -2
  105. package/dist/ui/assets/{run-sCrJ_ubn.js → run-BKqnD09g.js} +1 -1
  106. package/dist/ui/assets/{run-C_B85hI0.js → run-CGZiR3xh.js} +1 -1
  107. package/dist/ui/assets/{run-t2iWdfzq.js → run-Cka1zPhr.js} +1 -1
  108. package/dist/ui/assets/{run-DtX0LWhl.js → run-DGl39wQp.js} +1 -1
  109. package/dist/ui/assets/{run-BO_iDMDt.js → run-DHBSR6nH.js} +1 -1
  110. package/dist/ui/assets/{run-BP4EHoPn.js → run-DPEU0mPp.js} +2 -2
  111. package/dist/ui/assets/{session-command-memory-BUoZmF0a.js → session-command-memory-NaC_Sdn0.js} +2 -2
  112. package/dist/ui/assets/{session-jsonl-COjFh8nJ.js → session-jsonl-CK1nnZwL.js} +1 -1
  113. package/dist/ui/assets/{session-jsonl-DtX_kRi0.js → session-jsonl-iSVMsOx7.js} +1 -1
  114. package/dist/ui/assets/{session-search-index-C3kjN6bk.js → session-search-index-MELCgtJ1.js} +1 -1
  115. package/dist/ui/assets/{setup-feature-flags-remote-mzuU5Mr_.js → setup-feature-flags-remote-DGeAaOhE.js} +1 -1
  116. package/dist/ui/assets/{setup-model-catalog-BA5O4wQw.js → setup-model-catalog-B1uR2ova.js} +1 -1
  117. package/dist/ui/assets/{signed-fetch-CpUzKZ37.js → signed-fetch-bUBxSCEr.js} +1 -1
  118. package/dist/ui/assets/{silent-renew-backoff-Cdog3htH.js → silent-renew-backoff-k-_ROUgc.js} +1 -1
  119. package/dist/ui/assets/slicc-version-DwS1sP4C.js +1 -0
  120. package/dist/ui/assets/{snapshot-BkWKfawO.js → snapshot-DQ087sVE.js} +1 -1
  121. package/dist/ui/assets/{snapshot-features-hU8cpNhW.js → snapshot-features-DeJe8uji.js} +1 -1
  122. package/dist/ui/assets/{soundscape-DRe8SRya.js → soundscape-BnFzEyFw.js} +1 -1
  123. package/dist/ui/assets/{speak-BwLCgfXE.js → speak-dfd9BW6L.js} +1 -1
  124. package/dist/ui/assets/{sprinkle-manager-DCFW3izC.js → sprinkle-manager-yEepkH2c.js} +1 -1
  125. package/dist/ui/assets/{sprinkle-renderer-BFzdPmKP.js → sprinkle-renderer-k4VuPxiX.js} +1 -1
  126. package/dist/ui/assets/{state-Dcgx6XOM.js → state-DOWkJUEg.js} +1 -1
  127. package/dist/ui/assets/{store-pvF9f6f1.js → store-CD8HQQi0.js} +1 -1
  128. package/dist/ui/assets/{store-BdzJ0h6R.js → store-DMPHri86.js} +1 -1
  129. package/dist/ui/assets/{sudo-38TZOf-u.js → sudo-ChxuFQrD.js} +1 -1
  130. package/dist/ui/assets/{supervisor-CayzsIVi.js → supervisor-DsGHAeFb.js} +1 -1
  131. package/dist/ui/assets/{sync-dialog-D3Fmh-r-.js → sync-dialog-DJlE-_Qu.js} +1 -1
  132. package/dist/ui/assets/{transformers-env-B38XiP9h.js → transformers-env-CrD3F-k5.js} +1 -1
  133. package/dist/ui/assets/{tray-leader-Bo5fxfuc.js → tray-leader-Cj5ylIFZ.js} +1 -1
  134. package/dist/ui/assets/{tray-leave-runtime-BByz3P0z.js → tray-leave-runtime-B86ntR2C.js} +1 -1
  135. package/dist/ui/assets/{tray-sidecar-ClKbIPmM.js → tray-sidecar-BuTgfmG-.js} +1 -1
  136. package/dist/ui/assets/{upgrade-detection-BpESY2Aq.js → upgrade-detection-CdnTlzbk.js} +1 -1
  137. package/dist/ui/assets/{upskill-lick-skip-VuaP8Kyk.js → upskill-lick-skip-CwWHxj2D.js} +1 -1
  138. package/dist/ui/assets/{voice-reply-DVDUTe84.js → voice-reply-Ba6fFHwZ.js} +1 -1
  139. package/dist/ui/assets/{wasi-runtime-D8RXmh55.js → wasi-runtime-CelptKxl.js} +3 -3
  140. package/dist/ui/assets/{wc-attach-DzyHteCz.js → wc-attach-ClqP_QLV.js} +2 -2
  141. package/dist/ui/assets/{wc-browser-CdmU1-rf.js → wc-browser-DcJYOuwt.js} +1 -1
  142. package/dist/ui/assets/{wc-computers-Cj5kwEFf.js → wc-computers-DZ8F8APM.js} +1 -1
  143. package/dist/ui/assets/{wc-detached-sMJs044X.js → wc-detached-CqurijdL.js} +1 -1
  144. package/dist/ui/assets/{wc-extension-ClMdGHNz.js → wc-extension-upUnhYIA.js} +2 -2
  145. package/dist/ui/assets/{wc-follower-C4ucien1.js → wc-follower-lb7m208l.js} +3 -3
  146. package/dist/ui/assets/{wc-follower-oauth-DQnKSJbz.js → wc-follower-oauth-T2WVyWZD.js} +1 -1
  147. package/dist/ui/assets/{wc-gelatiere-BeWvDsHw.js → wc-gelatiere-DsLb5fF7.js} +1 -1
  148. package/dist/ui/assets/{wc-live-DGplanyK.js → wc-live-_82dhqx_.js} +5 -5
  149. package/dist/ui/assets/{wc-nav-BHYvwsgi.js → wc-nav-CWlfBhcg.js} +2 -2
  150. package/dist/ui/assets/{wc-onboarding-2FcRJ0RU.js → wc-onboarding-4dwuoncC.js} +2 -2
  151. package/dist/ui/assets/{wc-permissions-nQ50UHJK.js → wc-permissions-BxfEjmt-.js} +2 -2
  152. package/dist/ui/assets/{wc-placeholder-C1j8xlUn.js → wc-placeholder-Co5efYux.js} +3 -3
  153. package/dist/ui/assets/{wc-secret-request-oCK_9yth.js → wc-secret-request-BwJvFf3r.js} +1 -1
  154. package/dist/ui/assets/{wc-settings-BD7WIF4y.js → wc-settings-CIwPnJJ7.js} +3 -3
  155. package/dist/ui/assets/{wc-shell-PkaazcyQ.js → wc-shell-DAAFPsyf.js} +3 -3
  156. package/dist/ui/assets/{wc-shortcut-config-4DC-_Bcv.js → wc-shortcut-config-DIYpCAoK.js} +1 -1
  157. package/dist/ui/assets/{wc-sprinkles-BrDYA1Pp.js → wc-sprinkles-B6Lpsc3h.js} +2 -2
  158. package/dist/ui/assets/{wc-transcript-export-BOYgru6q.js → wc-transcript-export-Dk603hfy.js} +2 -2
  159. package/dist/ui/assets/{wc-tray-OPHqFp9o.js → wc-tray-tSlRM35e.js} +4 -4
  160. package/dist/ui/assets/welcome-detection-DZ0LEfXq.js +1 -0
  161. package/dist/ui/assets/{whisper-session-ZUe2wcTh.js → whisper-session-DO32dZVE.js} +2 -2
  162. package/dist/ui/assets/{xai-grok-D7lF9zW-.js → xai-grok-C5i9rD2B.js} +1 -1
  163. package/dist/ui/assets/{xai-grok-DbgWoTct.js → xai-grok-CGWMCUeC.js} +1 -1
  164. package/dist/ui/index.html +3 -3
  165. package/dist/ui/packages/webapp/index.html +3 -3
  166. package/package.json +2 -2
  167. package/dist/ui/assets/__vite-browser-external-BLOvPDKs.js +0 -1
  168. package/dist/ui/assets/fs-DgxmFERJ.js +0 -1
  169. package/dist/ui/assets/fs-DoQMygky.js +0 -2
  170. package/dist/ui/assets/provider-store-access-CP4SH953.js +0 -1
  171. package/dist/ui/assets/provider-store-access-CfVYpC6t.js +0 -1
  172. package/dist/ui/assets/slicc-version-L7NtPkOF.js +0 -1
  173. package/dist/ui/assets/welcome-detection-DIFKfeOe.js +0 -1
@@ -6,11 +6,13 @@
6
6
  * {@link SudoDecision}. They are deliberately injectable via {@link ExecFn} so
7
7
  * tests can assert the argv and parsing without spawning a real dialog.
8
8
  *
9
- * Fail-closed contract: a dismissed dialog, a non-zero exit, an unparsable
10
- * result, or a thrown error all resolve to `deny`. An `always` result with an
11
- * empty pattern falls back to the suggested default.
9
+ * Fail-closed contract: a dismissed dialog or Deny button is a bare `deny`
10
+ * (a human refused). A spawn/exec plumbing failure, or no native channel, is
11
+ * `{ decision: 'deny', reason: 'unavailable' }` so callers never treat infra
12
+ * failures as owner refusal. An `always` result with an empty pattern falls
13
+ * back to the suggested default.
12
14
  */
13
- import type { SudoApproveRequest, SudoBackend } from './types.js';
15
+ import { type SudoApproveRequest, type SudoBackend } from './types.js';
14
16
  /** Minimal exec seam: run a binary with args, resolve its stdout. */
15
17
  export type ExecFn = (cmd: string, args: string[]) => Promise<{
16
18
  stdout: string;
@@ -33,5 +35,8 @@ export declare function createPowerShellBackend(exec?: ExecFn): SudoBackend;
33
35
  export declare function createZenityBackend(exec?: ExecFn): SudoBackend;
34
36
  /** Linux GUI: `kdialog --warningyesnocancel` (+ `--inputbox`). */
35
37
  export declare function createKdialogBackend(exec?: ExecFn): SudoBackend;
36
- /** A backend that always denies — used when no native channel exists. */
38
+ /**
39
+ * A backend used when no native channel exists (headless, no DISPLAY/TTY).
40
+ * Returns `unavailable` — nobody was prompted — not a bare refuse.
41
+ */
37
42
  export declare function createDenyBackend(name?: string): SudoBackend;
@@ -6,12 +6,15 @@
6
6
  * {@link SudoDecision}. They are deliberately injectable via {@link ExecFn} so
7
7
  * tests can assert the argv and parsing without spawning a real dialog.
8
8
  *
9
- * Fail-closed contract: a dismissed dialog, a non-zero exit, an unparsable
10
- * result, or a thrown error all resolve to `deny`. An `always` result with an
11
- * empty pattern falls back to the suggested default.
9
+ * Fail-closed contract: a dismissed dialog or Deny button is a bare `deny`
10
+ * (a human refused). A spawn/exec plumbing failure, or no native channel, is
11
+ * `{ decision: 'deny', reason: 'unavailable' }` so callers never treat infra
12
+ * failures as owner refusal. An `always` result with an empty pattern falls
13
+ * back to the suggested default.
12
14
  */
13
15
  import { execFile as nodeExecFile } from 'child_process';
14
16
  import { promisify } from 'util';
17
+ import { unavailableDecision, } from './types.js';
15
18
  const defaultExec = promisify(nodeExecFile);
16
19
  /**
17
20
  * Human-readable description of the gated action, for every native dialog.
@@ -32,6 +35,29 @@ export function describeRequest(req) {
32
35
  function fallbackPattern(req) {
33
36
  return req.suggestedPattern?.trim() || req.detail.trim();
34
37
  }
38
+ /**
39
+ * Classify an exec failure from a real dialog binary.
40
+ *
41
+ * Node's `execFile` sets a **numeric** `code` when the process ran and exited
42
+ * non-zero (Deny / dismiss / Always-via-extra-button). A **string** errno
43
+ * (`ENOENT`, `EACCES`, …) or a generic `Error` without a numeric code means
44
+ * the binary never ran — plumbing, not a refusal.
45
+ */
46
+ function denyFromDialogFailure(err) {
47
+ const code = err?.code;
48
+ if (typeof code === 'number')
49
+ return { decision: 'deny' };
50
+ return unavailableDecision();
51
+ }
52
+ /**
53
+ * True when the dialog binary did not produce a real exit code.
54
+ * Matches {@link denyFromDialogFailure}: only a numeric `code` means the
55
+ * dialog ran (Deny / Always / dismiss). String errno or a code-less Error
56
+ * is plumbing — never a refusal.
57
+ */
58
+ function isSpawnFailure(err) {
59
+ return typeof err?.code !== 'number';
60
+ }
35
61
  /** macOS: single `osascript display dialog` with 3 buttons + a text field. */
36
62
  export function createOsascriptBackend(exec = defaultExec) {
37
63
  return {
@@ -53,8 +79,10 @@ export function createOsascriptBackend(exec = defaultExec) {
53
79
  }
54
80
  return { decision: 'deny' };
55
81
  }
56
- catch {
57
- return { decision: 'deny' };
82
+ catch (err) {
83
+ // User cancel (Escape) exits non-zero with a numeric code; spawn failure
84
+ // is ENOENT / generic Error → unavailable.
85
+ return denyFromDialogFailure(err);
58
86
  }
59
87
  },
60
88
  };
@@ -86,10 +114,12 @@ export function createPowerShellBackend(exec = defaultExec) {
86
114
  pattern: pattern && pattern.length > 0 ? pattern : suggested,
87
115
  };
88
116
  }
117
+ // MessageBox "No" prints DENY — a genuine refusal.
89
118
  return { decision: 'deny' };
90
119
  }
91
- catch {
92
- return { decision: 'deny' };
120
+ catch (err) {
121
+ // PowerShell MessageBox never throws on No; a throw is spawn/plumbing.
122
+ return denyFromDialogFailure(err);
93
123
  }
94
124
  },
95
125
  };
@@ -116,7 +146,9 @@ export function createZenityBackend(exec = defaultExec) {
116
146
  allowed = true;
117
147
  }
118
148
  catch (err) {
119
- // Non-zero exit: either Deny (no stdout) or the extra "Always" button.
149
+ // Spawn failure never reached a human; numeric exit is Deny / Always.
150
+ if (isSpawnFailure(err))
151
+ return unavailableDecision();
120
152
  always = stdoutOf(err).trim() === 'Always';
121
153
  allowed = always;
122
154
  }
@@ -146,6 +178,8 @@ export function createKdialogBackend(exec = defaultExec) {
146
178
  code = 0; // Yes
147
179
  }
148
180
  catch (err) {
181
+ if (isSpawnFailure(err))
182
+ return unavailableDecision();
149
183
  code = exitCodeOf(err);
150
184
  }
151
185
  if (code === 0)
@@ -156,12 +190,15 @@ export function createKdialogBackend(exec = defaultExec) {
156
190
  },
157
191
  };
158
192
  }
159
- /** A backend that always denies — used when no native channel exists. */
193
+ /**
194
+ * A backend used when no native channel exists (headless, no DISPLAY/TTY).
195
+ * Returns `unavailable` — nobody was prompted — not a bare refuse.
196
+ */
160
197
  export function createDenyBackend(name = 'none') {
161
198
  return {
162
199
  name,
163
200
  async prompt() {
164
- return { decision: 'deny' };
201
+ return unavailableDecision();
165
202
  },
166
203
  };
167
204
  }
@@ -8,10 +8,11 @@
8
8
  * implementation runs `window.prompt` inside a transient offscreen
9
9
  * `BrowserWindow`. Both seams are injectable so tests never touch Electron.
10
10
  *
11
- * Fail closed: a thrown dialog call, a cancelled prompt, or an unknown button
12
- * resolves to `deny` / falls back to the suggested pattern.
11
+ * Fail closed: a cancelled prompt or Deny button is a bare `deny`; a thrown
12
+ * `showMessageBox` (no display / Electron gone) is `unavailable`. A cancelled
13
+ * Always-pattern prompt falls back to the suggested pattern.
13
14
  */
14
- import type { SudoBackend } from './types.js';
15
+ import { type SudoBackend } from './types.js';
15
16
  /** Result shape of `dialog.showMessageBox`. */
16
17
  interface MessageBoxResult {
17
18
  response: number;
@@ -8,9 +8,11 @@
8
8
  * implementation runs `window.prompt` inside a transient offscreen
9
9
  * `BrowserWindow`. Both seams are injectable so tests never touch Electron.
10
10
  *
11
- * Fail closed: a thrown dialog call, a cancelled prompt, or an unknown button
12
- * resolves to `deny` / falls back to the suggested pattern.
11
+ * Fail closed: a cancelled prompt or Deny button is a bare `deny`; a thrown
12
+ * `showMessageBox` (no display / Electron gone) is `unavailable`. A cancelled
13
+ * Always-pattern prompt falls back to the suggested pattern.
13
14
  */
15
+ import { unavailableDecision, } from './types.js';
14
16
  const BUTTONS = ['Deny', 'Allow Once', 'Always'];
15
17
  /** Create the Electron backend. */
16
18
  export function createElectronBackend(deps = {}) {
@@ -45,7 +47,8 @@ export function createElectronBackend(deps = {}) {
45
47
  response = result.response;
46
48
  }
47
49
  catch {
48
- return { decision: 'deny' };
50
+ // Dialog never appeared — plumbing, not a refusal.
51
+ return unavailableDecision();
49
52
  }
50
53
  if (response === 1)
51
54
  return { decision: 'allow' };
@@ -8,11 +8,12 @@
8
8
  * (the agent's browser `node` shim can't reach this process) and returns the
9
9
  * human's decision. Loopback-only, like the other local node-server endpoints.
10
10
  *
11
- * Fail closed: an invalid body → 400; a backend that throws → `deny` (200).
12
- * Requests never auto-resolve to allow.
11
+ * Fail closed: an invalid body → 400; a backend that throws →
12
+ * `{ decision: 'deny', reason: 'unavailable' }` (200). A bare `deny` is reserved
13
+ * for a genuine human refusal. Requests never auto-resolve to allow.
13
14
  */
14
15
  import { type Express } from 'express';
15
- import type { SudoBackend } from './types.js';
16
+ import { type SudoBackend } from './types.js';
16
17
  export interface SudoEndpointOptions {
17
18
  /**
18
19
  * Backend to use for every request. Defaults to lazy per-request selection
@@ -8,12 +8,14 @@
8
8
  * (the agent's browser `node` shim can't reach this process) and returns the
9
9
  * human's decision. Loopback-only, like the other local node-server endpoints.
10
10
  *
11
- * Fail closed: an invalid body → 400; a backend that throws → `deny` (200).
12
- * Requests never auto-resolve to allow.
11
+ * Fail closed: an invalid body → 400; a backend that throws →
12
+ * `{ decision: 'deny', reason: 'unavailable' }` (200). A bare `deny` is reserved
13
+ * for a genuine human refusal. Requests never auto-resolve to allow.
13
14
  */
14
15
  import express from 'express';
15
16
  import { requireLoopback } from '../cloud-status.js';
16
17
  import { selectSudoBackend } from './select.js';
18
+ import { unavailableDecision, } from './types.js';
17
19
  // Must list EVERY `SudoKind`. An omission is not a safe default: the endpoint
18
20
  // 400s, the browser-side broker reads that as a denial, and the gate fails
19
21
  // closed forever — so a missing kind silently makes that whole approval path
@@ -68,8 +70,8 @@ export function registerSudoApproveEndpoint(app, options = {}) {
68
70
  decision = await backend.prompt(request);
69
71
  }
70
72
  catch (err) {
71
- warn(`sudo-approve backend "${backend.name}" threw — denying: ${String(err)}`);
72
- decision = { decision: 'deny' };
73
+ warn(`sudo-approve backend "${backend.name}" threw — denying as unavailable: ${String(err)}`);
74
+ decision = unavailableDecision();
73
75
  }
74
76
  res.json(decision);
75
77
  });
@@ -6,7 +6,7 @@
6
6
  * 3. Windows → PowerShell
7
7
  * 4. Linux+GUI → `zenity` / `kdialog`
8
8
  * 5. headless → editable TTY
9
- * 6. no channel → fail closed (deny) + log
9
+ * 6. no channel → fail closed (`unavailable`) + log
10
10
  *
11
11
  * The environment probe is injectable so tests can force any branch without
12
12
  * touching the real platform / spawning `which`.
@@ -6,7 +6,7 @@
6
6
  * 3. Windows → PowerShell
7
7
  * 4. Linux+GUI → `zenity` / `kdialog`
8
8
  * 5. headless → editable TTY
9
- * 6. no channel → fail closed (deny) + log
9
+ * 6. no channel → fail closed (`unavailable`) + log
10
10
  *
11
11
  * The environment probe is injectable so tests can force any branch without
12
12
  * touching the real platform / spawning `which`.
@@ -4,10 +4,12 @@
4
4
  * When there's no GUI but the node-server has a controlling terminal, the
5
5
  * approval gesture is a keystroke on stdin: `[a]llow once / [d]eny / [A]lways`.
6
6
  * Choosing "Always" then reads an editable pattern line (blank keeps the
7
- * suggested default). Fail closed: a closed stream, EOF, or any error denies.
7
+ * suggested default). Fail closed: an explicit `d` / other keystroke is a bare
8
+ * deny; a closed stream, EOF mid-prompt, or any thrown error is `unavailable`
9
+ * (the request never reached an answering human).
8
10
  */
9
11
  import type { Interface as ReadlineInterface } from 'readline';
10
- import type { SudoBackend } from './types.js';
12
+ import { type SudoBackend } from './types.js';
11
13
  /** Seam for tests: build a question-asking interface over arbitrary streams. */
12
14
  export interface TtyDeps {
13
15
  output?: NodeJS.WritableStream;
@@ -4,9 +4,12 @@
4
4
  * When there's no GUI but the node-server has a controlling terminal, the
5
5
  * approval gesture is a keystroke on stdin: `[a]llow once / [d]eny / [A]lways`.
6
6
  * Choosing "Always" then reads an editable pattern line (blank keeps the
7
- * suggested default). Fail closed: a closed stream, EOF, or any error denies.
7
+ * suggested default). Fail closed: an explicit `d` / other keystroke is a bare
8
+ * deny; a closed stream, EOF mid-prompt, or any thrown error is `unavailable`
9
+ * (the request never reached an answering human).
8
10
  */
9
11
  import { createInterface } from 'readline';
12
+ import { unavailableDecision, } from './types.js';
10
13
  export function defaultRl() {
11
14
  return createInterface({ input: process.stdin, output: process.stdout });
12
15
  }
@@ -40,7 +43,7 @@ export function createTtyBackend(deps = {}) {
40
43
  return { decision: 'deny' };
41
44
  }
42
45
  catch {
43
- return { decision: 'deny' };
46
+ return unavailableDecision();
44
47
  }
45
48
  finally {
46
49
  try {
@@ -43,11 +43,39 @@ export interface SudoApproveRequest {
43
43
  export interface SudoDecision {
44
44
  decision: 'allow' | 'deny' | 'always';
45
45
  pattern?: string;
46
+ /**
47
+ * Why a `deny` was reached when nobody actually refused. Absent for a real
48
+ * gesture (Deny button, TTY `d`, dialog dismiss). Enforcement layers and
49
+ * biscotto review use it to tell a guest "unanswered", not "refused".
50
+ *
51
+ * Node-server only stamps `unavailable` (no approval surface, a dialog
52
+ * binary that could not be spawned, a backend that threw). Timeout reasons
53
+ * live in the webapp broker layer. Mirror of webapp `SudoDecision.reason`.
54
+ *
55
+ * Deliberately a field rather than a fourth `decision` value: every consumer
56
+ * branches on `decision === 'deny'`, so a new variant would fail OPEN.
57
+ */
58
+ reason?: SudoUnansweredReason;
46
59
  }
60
+ /**
61
+ * Why a `deny` carries no human's refusal. See {@link SudoDecision.reason}.
62
+ * Every value means "unanswered"; only the absence of a reason means "refused".
63
+ * Node-server emits only `unavailable`; the timeout values are listed so the
64
+ * wire shape matches the webapp consumer.
65
+ */
66
+ export type SudoUnansweredReason = 'user-timeout' | 'cone-timeout' | 'unavailable';
67
+ /**
68
+ * The fail-closed decision for a request that never reached an approver.
69
+ * Use this instead of a bare `{ decision: 'deny' }` on any plumbing path: a
70
+ * bare deny is indistinguishable from a human pressing "Deny".
71
+ */
72
+ export declare function unavailableDecision(): SudoDecision;
47
73
  /**
48
74
  * A native approval channel. `name` is for logging/selection; `prompt` raises
49
75
  * the actual gesture. Implementations MUST fail closed (resolve `deny`) on any
50
- * error, dismissal, or timeout — never throw to the endpoint.
76
+ * error, dismissal, or timeout — never throw to the endpoint. Plumbing
77
+ * failures MUST carry {@link unavailableDecision}'s `reason`; only a genuine
78
+ * human refusal is a bare deny.
51
79
  */
52
80
  export interface SudoBackend {
53
81
  readonly name: string;
@@ -10,4 +10,11 @@
10
10
  * gesture in this process — the agent's in-browser `node` shim cannot reach
11
11
  * here.
12
12
  */
13
- export {};
13
+ /**
14
+ * The fail-closed decision for a request that never reached an approver.
15
+ * Use this instead of a bare `{ decision: 'deny' }` on any plumbing path: a
16
+ * bare deny is indistinguishable from a human pressing "Deny".
17
+ */
18
+ export function unavailableDecision() {
19
+ return { decision: 'deny', reason: 'unavailable' };
20
+ }