@fungi.computer/truffle 0.0.0-stage → 0.2.0

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +542 -2
  3. package/computerd-loopback.cjs +16 -0
  4. package/dist/auth-cli.d.ts +46 -0
  5. package/dist/auth-cli.js +373 -0
  6. package/dist/fungi-account-command.d.ts +18 -0
  7. package/dist/fungi-account-command.js +95 -0
  8. package/dist/index.d.ts +27 -0
  9. package/dist/index.js +23 -0
  10. package/dist/internal/image-fit-metafile.json +106 -0
  11. package/dist/internal/local-image-fit-worker.mjs +960 -0
  12. package/dist/internal/private-workerd-sandbox-worker.mjs +27487 -0
  13. package/dist/internal/sandbox-metafile.json +6726 -0
  14. package/dist/linear-auth.d.ts +4 -0
  15. package/dist/linear-auth.js +72 -0
  16. package/dist/local-archive-handoff.d.ts +12 -0
  17. package/dist/local-archive-handoff.js +21 -0
  18. package/dist/local-extensions.d.ts +143 -0
  19. package/dist/local-extensions.js +624 -0
  20. package/dist/local-image-fit.d.ts +30 -0
  21. package/dist/local-image-fit.js +105 -0
  22. package/dist/local-login.d.ts +55 -0
  23. package/dist/local-login.js +167 -0
  24. package/dist/local-models.d.ts +16 -0
  25. package/dist/local-models.js +82 -0
  26. package/dist/local-session-archives.d.ts +8 -0
  27. package/dist/local-session-archives.js +152 -0
  28. package/dist/local-skill-source.d.ts +21 -0
  29. package/dist/local-skill-source.js +433 -0
  30. package/dist/log-event.d.ts +9 -0
  31. package/dist/log-event.js +13 -0
  32. package/dist/private-bounded-response.d.ts +2 -0
  33. package/dist/private-bounded-response.js +41 -0
  34. package/dist/private-bun-shiitake-host.d.ts +11 -0
  35. package/dist/private-bun-shiitake-host.js +66 -0
  36. package/dist/private-file-credential-store.d.ts +58 -0
  37. package/dist/private-file-credential-store.js +331 -0
  38. package/dist/private-files-patch.d.ts +4 -0
  39. package/dist/private-files-patch.js +104 -0
  40. package/dist/private-fungi-account.d.ts +252 -0
  41. package/dist/private-fungi-account.js +1329 -0
  42. package/dist/private-fungi-auth-error.d.ts +5 -0
  43. package/dist/private-fungi-auth-error.js +7 -0
  44. package/dist/private-fungi-mcp-connection.d.ts +26 -0
  45. package/dist/private-fungi-mcp-connection.js +162 -0
  46. package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
  47. package/dist/private-fungi-mcp-endpoint.js +15 -0
  48. package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
  49. package/dist/private-fungi-mcp-selection-schema.js +15 -0
  50. package/dist/private-fungi-mcp-selection.d.ts +14 -0
  51. package/dist/private-fungi-mcp-selection.js +49 -0
  52. package/dist/private-fungi-model-api.d.ts +6 -0
  53. package/dist/private-fungi-model-api.js +125 -0
  54. package/dist/private-fungi-model-catalog.d.ts +16 -0
  55. package/dist/private-fungi-model-catalog.js +86 -0
  56. package/dist/private-fungi-model-provider.d.ts +6 -0
  57. package/dist/private-fungi-model-provider.js +51 -0
  58. package/dist/private-fungi-pending-logouts.d.ts +34 -0
  59. package/dist/private-fungi-pending-logouts.js +135 -0
  60. package/dist/private-fungi-service-logout.d.ts +44 -0
  61. package/dist/private-fungi-service-logout.js +59 -0
  62. package/dist/private-invocation-computer.d.ts +58 -0
  63. package/dist/private-invocation-computer.js +498 -0
  64. package/dist/private-invocation-files.d.ts +30 -0
  65. package/dist/private-invocation-files.js +520 -0
  66. package/dist/private-local-acp-stdio.d.ts +17 -0
  67. package/dist/private-local-acp-stdio.js +145 -0
  68. package/dist/private-local-admission-gate.d.ts +14 -0
  69. package/dist/private-local-admission-gate.js +52 -0
  70. package/dist/private-local-agent-control.d.ts +101 -0
  71. package/dist/private-local-agent-control.js +30 -0
  72. package/dist/private-local-agent-default-model.d.ts +12 -0
  73. package/dist/private-local-agent-default-model.js +31 -0
  74. package/dist/private-local-auth-rpc.d.ts +129 -0
  75. package/dist/private-local-auth-rpc.js +396 -0
  76. package/dist/private-local-bwrap.d.ts +13 -0
  77. package/dist/private-local-bwrap.js +70 -0
  78. package/dist/private-local-client-version.d.ts +5 -0
  79. package/dist/private-local-client-version.js +43 -0
  80. package/dist/private-local-computer-attach.d.ts +49 -0
  81. package/dist/private-local-computer-attach.js +230 -0
  82. package/dist/private-local-computer-backend.d.ts +282 -0
  83. package/dist/private-local-computer-backend.js +402 -0
  84. package/dist/private-local-computer-processes.d.ts +202 -0
  85. package/dist/private-local-computer-processes.js +613 -0
  86. package/dist/private-local-config-root.d.ts +21 -0
  87. package/dist/private-local-config-root.js +19 -0
  88. package/dist/private-local-containment.d.ts +51 -0
  89. package/dist/private-local-containment.js +161 -0
  90. package/dist/private-local-default-model.d.ts +11 -0
  91. package/dist/private-local-default-model.js +45 -0
  92. package/dist/private-local-delegation.d.ts +36 -0
  93. package/dist/private-local-delegation.js +133 -0
  94. package/dist/private-local-effective-model.d.ts +27 -0
  95. package/dist/private-local-effective-model.js +17 -0
  96. package/dist/private-local-exec-environment.d.ts +16 -0
  97. package/dist/private-local-exec-environment.js +11 -0
  98. package/dist/private-local-exec-launch.d.ts +41 -0
  99. package/dist/private-local-exec-launch.js +51 -0
  100. package/dist/private-local-exec-path.d.ts +6 -0
  101. package/dist/private-local-exec-path.js +40 -0
  102. package/dist/private-local-git-exclude.d.ts +2 -0
  103. package/dist/private-local-git-exclude.js +33 -0
  104. package/dist/private-local-http-dispatch.d.ts +5 -0
  105. package/dist/private-local-http-dispatch.js +14 -0
  106. package/dist/private-local-linux-sandbox.d.ts +8 -0
  107. package/dist/private-local-linux-sandbox.js +67 -0
  108. package/dist/private-local-mail.d.ts +13 -0
  109. package/dist/private-local-mail.js +77 -0
  110. package/dist/private-local-mesh-enrollment.d.ts +34 -0
  111. package/dist/private-local-mesh-enrollment.js +188 -0
  112. package/dist/private-local-mesh-links.d.ts +21 -0
  113. package/dist/private-local-mesh-links.js +229 -0
  114. package/dist/private-local-mesh-record.d.ts +120 -0
  115. package/dist/private-local-mesh-record.js +55 -0
  116. package/dist/private-local-process-identity.d.ts +20 -0
  117. package/dist/private-local-process-identity.js +40 -0
  118. package/dist/private-local-runtime-host.d.ts +192 -0
  119. package/dist/private-local-runtime-host.js +1177 -0
  120. package/dist/private-local-sandbox-policy.d.ts +21 -0
  121. package/dist/private-local-sandbox-policy.js +121 -0
  122. package/dist/private-local-sandbox.d.ts +37 -0
  123. package/dist/private-local-sandbox.js +114 -0
  124. package/dist/private-local-seatbelt.d.ts +24 -0
  125. package/dist/private-local-seatbelt.js +68 -0
  126. package/dist/private-local-service-contract.d.ts +170 -0
  127. package/dist/private-local-service-contract.js +1 -0
  128. package/dist/private-local-service-discovery.d.ts +217 -0
  129. package/dist/private-local-service-discovery.js +322 -0
  130. package/dist/private-local-service-ids.d.ts +3 -0
  131. package/dist/private-local-service-ids.js +8 -0
  132. package/dist/private-local-service-lease.d.ts +16 -0
  133. package/dist/private-local-service-lease.js +105 -0
  134. package/dist/private-local-service-listener.d.ts +22 -0
  135. package/dist/private-local-service-listener.js +407 -0
  136. package/dist/private-local-service-log.d.ts +128 -0
  137. package/dist/private-local-service-log.js +321 -0
  138. package/dist/private-local-service-owner.d.ts +135 -0
  139. package/dist/private-local-service-owner.js +1369 -0
  140. package/dist/private-local-service-process.d.ts +1 -0
  141. package/dist/private-local-service-process.js +8 -0
  142. package/dist/private-local-service-termination.d.ts +7 -0
  143. package/dist/private-local-service-termination.js +67 -0
  144. package/dist/private-local-service.d.ts +46 -0
  145. package/dist/private-local-service.js +606 -0
  146. package/dist/private-local-session-discovery.d.ts +66 -0
  147. package/dist/private-local-session-discovery.js +278 -0
  148. package/dist/private-local-shiitake-host.d.ts +44 -0
  149. package/dist/private-local-shiitake-host.js +143 -0
  150. package/dist/private-local-startup-model.d.ts +51 -0
  151. package/dist/private-local-startup-model.js +86 -0
  152. package/dist/private-local-stop-all.d.ts +27 -0
  153. package/dist/private-local-stop-all.js +26 -0
  154. package/dist/private-local-ui-attachment.d.ts +19 -0
  155. package/dist/private-local-ui-attachment.js +71 -0
  156. package/dist/private-local-worker-server.d.ts +16 -0
  157. package/dist/private-local-worker-server.js +144 -0
  158. package/dist/private-mcp-form-contract.d.ts +186 -0
  159. package/dist/private-mcp-form-contract.js +53 -0
  160. package/dist/private-mcp-form-elicitation.d.ts +18 -0
  161. package/dist/private-mcp-form-elicitation.js +97 -0
  162. package/dist/private-mcp-form-session.d.ts +172 -0
  163. package/dist/private-mcp-form-session.js +148 -0
  164. package/dist/private-openai-claims.d.ts +30 -0
  165. package/dist/private-openai-claims.js +69 -0
  166. package/dist/private-oyster-mcp-auth.d.ts +13 -0
  167. package/dist/private-oyster-mcp-auth.js +57 -0
  168. package/dist/private-provider-rejection.d.ts +15 -0
  169. package/dist/private-provider-rejection.js +24 -0
  170. package/dist/private-sqlite-owner.d.ts +39 -0
  171. package/dist/private-sqlite-owner.js +169 -0
  172. package/dist/private-team-computers.d.ts +38 -0
  173. package/dist/private-team-computers.js +110 -0
  174. package/dist/private-workerd-sandbox.d.ts +3 -0
  175. package/dist/private-workerd-sandbox.js +347 -0
  176. package/dist/seatbelt/safe-base.sbpl +79 -0
  177. package/dist/subscription-activation.d.ts +9 -0
  178. package/dist/subscription-activation.js +62 -0
  179. package/dist/subscription-identity.d.ts +23 -0
  180. package/dist/subscription-identity.js +124 -0
  181. package/dist/subscription-turn.d.ts +17 -0
  182. package/dist/subscription-turn.js +143 -0
  183. package/dist/team-apps.d.ts +24 -0
  184. package/dist/team-apps.js +128 -0
  185. package/dist/tracker-git-credential.d.ts +15 -0
  186. package/dist/tracker-git-credential.js +87 -0
  187. package/native/bin/LICENSE.bubblewrap +502 -0
  188. package/native/bin/LICENSE.libcap +112 -0
  189. package/native/bin/LICENSE.sandbox-runtime +201 -0
  190. package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
  191. package/native/bin/build.json +4037 -0
  192. package/native/bin/bwrap-build-source/Dockerfile +48 -0
  193. package/native/bin/bwrap-build-source/README.md +96 -0
  194. package/native/bin/bwrap-build-source/build.mjs +52 -0
  195. package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
  196. package/native/bin/bwrap-build-source/pin.mjs +21 -0
  197. package/native/bin/bwrap-build-source/record.mjs +74 -0
  198. package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
  199. package/native/bin/bwrap-build-source/vendor/README.md +23 -0
  200. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
  201. package/native/bin/bwrap-build.json +44 -0
  202. package/native/bin/fungi-apply-patch +0 -0
  203. package/native/bin/fungi-bwrap +0 -0
  204. package/native/bin/fungi-bwrap.sha256 +1 -0
  205. package/native/bin/fungi-seccomp.bpf +0 -0
  206. package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
  207. package/package.json +161 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jamstack Consulting LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,543 @@
1
- # Temporary Holding Version
1
+ # Local runtime
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This private runtime hosts Agents and saved Sessions on the same user's machine.
4
+ The Fungi terminal consumes it through a persistent local service. It owns
5
+ SQLite storage, model credentials, project Files and shell operations, not
6
+ terminal rendering. It is an internal workspace, not a standalone npm
7
+ installation path.
8
+
9
+ ## Source setup
10
+
11
+ Use a configured repository checkout with the pinned Node, Bun and pnpm tools.
12
+ Local runs its own host build of the native `apply_patch` engine, which
13
+ [`@fungi.computer/cordyceps`](../../packages/cordyceps/README.md#native-patch-engine)
14
+ owns; Cordyceps' build prerequisites apply. Run from the repository root:
15
+
16
+ ```sh
17
+ pnpm turbo:run run build --filter=@fungi.computer/truffle
18
+ pnpm --filter @fungi.computer/truffle test
19
+ ```
20
+
21
+ The build places the engine in `native/bin` through Cordyceps'
22
+ `fungi-apply-patch-build` and Local's dependency closure, then the tests run its
23
+ unit and native patch tests. They do not sign in to a provider or start a
24
+ customer Agent. Build outputs and test fixtures are disposable. Without a built
25
+ engine, Local offers no `apply_patch` tool.
26
+
27
+ ## Host and Session lifetime
28
+
29
+ [openPrivateLocalRuntimeHost](src/private-local-runtime-host.ts) captures the
30
+ canonical project root and opens the Agent's SQLite store, Shiitake owner, model
31
+ source and tool resources. A fresh Agent store lives under the project's
32
+ `.fungi/agents/`. Reopening a store retains its Agent identity and Sessions.
33
+ Corrupt, unreadable or already-owned stores remain explicit failures, not
34
+ reasons to silently create a replacement.
35
+
36
+ [openPrivateLocalShiitakeHost](src/private-local-shiitake-host.ts) mounts the
37
+ ordinary Shiitake HTTP routes through an injected loopback server opener and
38
+ returns the neutral client and `main` Session. Closing it releases the client,
39
+ transport and owner. The worker-batch variant owns several named routes on one
40
+ server. Closing the batch closes their resources.
41
+
42
+ The [service manager](src/private-local-service.ts) discovers or starts the same
43
+ user's shared background service. It selects project stores and creates or
44
+ resumes Sessions through that owner. Closing a client connection does not stop
45
+ the service or erase its Session. The CLI's `fungi stop` sends the service
46
+ SIGTERM, which drains its owners; a service still running 5 s later is
47
+ SIGKILLed, and its `service.json` and owner lock are removed either way. A
48
+ SIGKILL ends only the service process: the shell and exec commands its Agents
49
+ started run detached in their own process groups and can keep running after it
50
+ ([client-surfaces-v1-79](../../issues/client-surfaces-v1/79-stop-the-service-process-groups-on-fungi-stop.md)).
51
+ Interrupted turns resume when the service next starts.
52
+
53
+ The service releases an Agent nothing is using. It counts each request and each
54
+ open Session watch on that Agent, and every mail or control operation that
55
+ reaches it. An unused Agent with no running or waiting Session, Watchdog job,
56
+ scheduled wake, reply still owed a delivery attempt or running background
57
+ process is closed after two consecutive idle checks (one per minute). Delivered
58
+ reply receipts and dead letters stay in the outbox but owe nothing. Work an
59
+ Agent delegated is owed by its child, whose reply mail reopens the parent. It is
60
+ then dropped from `~/.fungi/service/owners.json`. The next request, mail or send
61
+ reopens it. Shutdown keeps only Agents with work in that file, so a restart
62
+ reopens just those. A released Agent's per-Session client PATH binding and
63
+ exited background-process logs end with it.
64
+
65
+ `~/.fungi/service/service.json` and the service's `status` route record its
66
+ `version` (the packed CLI's content hash, or `source`) and `protocol` (the
67
+ client-service wire contract). Every TUI or CLI start ensures its own build: it
68
+ reuses a service of the same version, and otherwise stops the registered one as
69
+ `fungi stop` does and starts its own. A window that has already reached a
70
+ service never replaces one: it keeps a newer build that speaks its protocol, and
71
+ for another protocol shows only "The Fungi service was updated. Restart this
72
+ window." Saved Sessions remain on disk throughout.
73
+
74
+ Version policy: a fresh launch of a different build replaces the running
75
+ service, so use one installed build per service directory (`~/.fungi/service`).
76
+ Every source checkout counts as the same `source` version, so restart the
77
+ service (`fungi stop`) after editing the source.
78
+
79
+ ### Service diagnostics candidate
80
+
81
+ `private-local-service-log.ts` owns the service's JSON record contract, native
82
+ file sink and always-on five-minute sampler. It retains all maintained `bun:jsc`
83
+ type counts when the encoded record fits 32 KiB. If not, it emits an explicit
84
+ map omission summary rather than selecting a top-N subset or truncating JSON.
85
+ Counts include uncollected allocations; they are not retained-root evidence.
86
+ Unavailable statistics are explicit, never zero substitutes.
87
+
88
+ CTO ruling `dc105` selects the runtime's own filesystem primitives, owned by
89
+ this same module. The sink opens append with mode `0600` and verifies the
90
+ descriptor without chmod repair, checks byte counts on every write, and rotates
91
+ using checked native renames. No logger framework, worker, retry loop or second
92
+ store is part of this contract.
93
+
94
+ The candidate policy is one active 1 MiB file plus three 1 MiB backups, not a
95
+ four-backup policy. Disposal must precede the existing owner lease release. No
96
+ inspector, environment debug switch, forced GC, new service supervisor or
97
+ memory-leak fix is implied. The separate 4,096-cycle memory experiment did not
98
+ reproduce the reported sustained growth.
99
+
100
+ The diagnostics resource records startup/ready and observed stop reasons and
101
+ owns only its sampler/writer. Both entry paths use the existing listener owner,
102
+ which disposes diagnostics before registration removal and lease release.
103
+ Diagnostics I/O faults do not stop the service, turns or normal shutdown.
104
+
105
+ Native fixtures allocate only under this worktree's ignored
106
+ `.botanical/service-log-fixtures/`; child HOME and TMPDIR are fixture-owned.
107
+ Primitive-only native tests use the diagnostics resource directly; later
108
+ service/CLI/doctor proofs load the real consumer closure separately. Short-count
109
+ injection controls the maintained SDK return value; it is not an OS-generated
110
+ short-write claim. Rename-fault tests use an actual filesystem collision.
111
+
112
+ CTO decision `ee8f5320e0de` selects continued service operation with explicit
113
+ failed diagnostics. The first sink fault latches a bounded code and stable
114
+ first-failure ISO timestamp, stops the sampler and attempts writer disposal
115
+ once. Later requests read that current resource state, not the startup
116
+ connection snapshot. Opening/writing/closing failures are distinct; a failed
117
+ heap read emits `memory-unavailable`, never zero statistics. No retries,
118
+ separate journal or store, environment switch or new runtime owner is added.
119
+
120
+ Protocol 2 requires diagnostics on live `/status` and `/inspect` responses; the
121
+ registration file remains identity-only. The maintained Zod live decoder rejects
122
+ missing diagnostics rather than assuming ACTIVE. `fungi doctor` performs only
123
+ the manager's live status read and prints `FAILED <code> since <timestamp>` (or
124
+ ACTIVE). It never starts/restarts the service.
125
+
126
+ The earlier LogTape probe was rejected on native mode and swallowed-rename
127
+ failures; it is not an adoption option. Its old candidate tests are superseded
128
+ by native-fs tests, with the historical probe preserved separately. Dependency
129
+ removal uses the maintained package operation, not hand-edited lock state.
130
+ Focused native tests do not qualify the service/CLI/doctor consumer closure or
131
+ authorize production enablement; those remain subject to the built-dependency
132
+ merge gate.
133
+
134
+ The service acquires its SQLite owner row and writes its PID and process-start
135
+ identity in the same transaction. `service.json` is only a pointer to that row;
136
+ a matching file or HTTP handshake alone does not authorize a signal. Before
137
+ stopping, the client requires the file to match the live lease owner and checks
138
+ that process's start identity (Linux `/proc/<pid>/stat` `starttime`). It checks
139
+ the process identity again before each signal. These are separate reads and
140
+ `kill` calls: a residual read-to-signal PID-reuse window remains; this is not an
141
+ atomic kernel process handle.
142
+
143
+ On macOS, `ps lstart` has only one-second resolution. No sub-second macOS reader
144
+ is implemented, so `fungi stop` and automatic build replacement **refuse to
145
+ signal** there with an actionable message. Close the service manually before
146
+ launching another build. A possibly live lease is retained conservatively on
147
+ platforms without verifiable start identity, even if its PID may have been
148
+ reused.
149
+
150
+ A Session keeps its invoking client's validated executable PATH while its owner
151
+ is live. Reconnecting the same live Session with a different PATH is a conflict.
152
+ Stop the service before deliberately rebinding that Session to a different
153
+ toolchain. Other caller environment variables are not imported.
154
+
155
+ ## Files, tools and models
156
+
157
+ The local Computer is explicit in execute: `env.computers.local.exec(...)` and
158
+ `env.computers.local.files.read/find/edit(...)`. There is no shared `files`
159
+ module or Computer role named Home. The host grants the product Home extension
160
+ its already-rooted port; Home adopts the single local attachment and supplies
161
+ direct `read`, `list`, `glob`, `grep` and native `apply_patch` tools. The same
162
+ tool gate admits each direct call. `/home local` is a human command; there is no
163
+ model operation that changes Home.
164
+
165
+ - [Files](src/private-invocation-files.ts) captures the project root and rejects
166
+ path or symlink escapes. Native patch follows its own stricter rooted
167
+ contract.
168
+ - [Shell commands](src/private-invocation-computer.ts) run POSIX `sh` with the
169
+ OS user's access, a rooted cwd and bounded output. Files containment is not a
170
+ claim that arbitrary shell commands are an OS sandbox. `exec`'s `run` input is
171
+ one Zod boundary over two mutually exclusive forms: the shell form (`command`,
172
+ interpreted by `sh -c`) and a direct form (`executable` and `args`, run with
173
+ no shell interpretation at all, through the same bounded native process owner
174
+ `runPrivateInvocationProcess`). Zod refuses a request carrying both or
175
+ neither. Either form accepts optional `stdin`
176
+ ([its own bound](src/private-local-exec-launch.ts), not borrowed from Files),
177
+ written to the child once, then closed; there is no interactive stdin. An
178
+ omitted `stdin` closes immediately rather than leaving an open pipe, so a
179
+ reader such as `cat` sees EOF instead of hanging. A direct-form executable the
180
+ host cannot find or run reports a typed refusal naming it, rather than a
181
+ generic sandbox failure.
182
+ - [Background processes](src/private-local-computer-processes.ts) support start,
183
+ poll, wait, list and kill on the existing exec surface. `start` takes the same
184
+ shell-or-direct, optional-stdin input as `run`, through the existing
185
+ background process owner (`ManagedProcess`/`spawn`, not `run`'s
186
+ `runPrivateInvocationProcess`). Wait is bounded to 60 seconds. Cancelling a
187
+ wait does not kill the process. Output retains a bounded tail with offsets.
188
+ Session deletion or owner close stops its groups. Process IDs do not survive
189
+ host restart.
190
+ - [Guest execution](src/private-workerd-sandbox.ts) uses the maintained workerd
191
+ sandbox and admitted Mycelium capabilities. It reports typed boundary failures
192
+ separately from the native command's result.
193
+ - [Models](src/local-models.ts) uses Pi's registry and Veil's allowed login
194
+ methods with the local credential store. Fungi's provider reads its hosted
195
+ catalog through the selected Team and uses Hub billing. Model choice follows a
196
+ Session override, then the Agent's default, then an available provider login;
197
+ there is no per-person remembered model. `/model` in a chat sets that Agent's
198
+ default and thinking level and clears the Session override, so the running
199
+ chat and every new chat with that Agent use it; another Agent is unaffected.
200
+ Agent defaults stay in their Agent store.
201
+ [One resolver](src/private-local-startup-model.ts) decides a new chat's model
202
+ for both a fresh Session and the landing, reading the stored default through
203
+ the same reader as Agent discovery (a store that predates the schema fails
204
+ loudly). The host validates offered models and supported thinking levels.
205
+ `agents.models` lists the `provider/model` addresses offered by signed-in or
206
+ configured providers. `agents.create` accepts an optional name, an optional
207
+ address from that list and an optional thinking level; the new Agent works in
208
+ the project folder and the result returns it with the resolved model.
209
+ `agents.setModel` changes the Agent's default, optionally with a thinking
210
+ level, without replacing Session overrides. An address Local does not offer
211
+ fails as a typed `model:rejected` capability failure and changes nothing.
212
+ Creation and sends refuse missing provider login; failed runs keep a safe,
213
+ actionable diagnostic for `agents.wait`. A hosted Team's key-source choice
214
+ remains an explicit restriction, including when a manual model is selected.
215
+ - Archiving tidies a folder's Agents without deleting any. The archived flag
216
+ lives in the Agent's own store next to `created_by`, so the store, its
217
+ Sessions and files stay. An Agent can `agents.archive` and `agents.unarchive`
218
+ an Agent it created, never another Agent or itself; a person runs
219
+ `fungi agents archive|unarchive <id>` for any Agent or presses `a` on the
220
+ landing Agent picker. An archived Agent leaves `agents.list`, discovery (the
221
+ picker and newest-Session resolution) and delegation: `agents.send` to it
222
+ fails as `archive:archived`. `agents.list` with `archived: true`, or
223
+ `fungi agents list --archived`, finds it to restore. Archiving is refused,
224
+ naming the running command, while any Session of the Agent has queued or
225
+ running work, or while it still waits for work it delegated
226
+ (`archive:pending`); the Agent cancels or waits first. Archive and admission
227
+ are serialized per Agent, so a send racing an archive either runs first (and
228
+ the archive is refused) or is refused as archived. A store written before the
229
+ flag reads as unreadable, with the phrase to move it aside; archiving it fails
230
+ as `archive:unreadable`.
231
+ - [Delegation](src/private-local-delegation.ts) gives each project Agent the
232
+ Hub's `followUp` delegation over its own store. `agents.send` freezes BirdDog
233
+ mail through `fleet.start` and returns the child's `commandId`. The
234
+ [service owner](src/private-local-service-owner.ts) routes mail between its
235
+ project Agents and opens an idle addressee, so a reply wakes an idle parent;
236
+ an active parent runs it next, after its current work. An Agent admits only
237
+ new work that replies to its sender, or a reply that `fleet.authenticateReply`
238
+ verifies against the mail it sent. That reply is authored by the child,
239
+ carries `meta.delegation.reply`, and reaches the model inside
240
+ `<fungi_agent_reply>`. Every other mail is refused.
241
+
242
+ Root `AGENTS.md` is captured through the rooted Files owner for run
243
+ instructions. Missing optional instructions are different from unreadable,
244
+ oversized or escaping files. This instruction port stays tied to the project
245
+ root independently of Home.
246
+
247
+ [Skills](src/local-skill-source.ts) use the product extension's `prompt` and
248
+ `run_tools` hooks. The prompt lists names and descriptions;
249
+ `skill.load({name, file?})` reads instructions or a supporting file through
250
+ `execute`. Bodies stay tool output. Discovery diagnostics distinguish empty
251
+ roots from skipped or rejected entries and explain them in the skills section.
252
+ Raw causes remain redacted in local service logs.
253
+
254
+ Discovery follows Pi 0.73.1: project `.fungi/skills`, then `.agents/skills` from
255
+ the project folder up to its git root, then personal `~/.fungi/skills` and
256
+ `~/.agents/skills`. Project roots precede personal roots. Claude and Pi folders
257
+ are not defaults. Extra folders are opt-in through a `skills` list in
258
+ `.fungi/settings.json` or `~/.fungi/settings.json`, for example:
259
+
260
+ ```json
261
+ { "skills": ["~/.claude/skills"] }
262
+ ```
263
+
264
+ Project settings paths are relative to the project; personal settings paths are
265
+ relative to `~/.fungi`. `~/` expands to the user's home. Project extra folders
266
+ precede personal roots. The first name wins; collisions warn. A directory with
267
+ `SKILL.md` is one skill and stops discovery below it; other directories are
268
+ searched recursively, excluding dot directories and `node_modules`. This also
269
+ finds Claude's `synced/<id-pair>/<skill>/SKILL.md` layout when opted in.
270
+
271
+ A non-empty description is required. Name-rule violations and descriptions over
272
+ 1,024 characters warn without rejecting the skill. Only a literal YAML
273
+ `disable-model-invocation: true` hides it from model discovery. Discovery stays
274
+ inside the canonical root, including symlink targets, with a 16-level depth and
275
+ 512-entry limit per root. Reads remain inside each selected skill's real
276
+ directory, with the existing file and catalog size limits. Scripts are listed,
277
+ never executed by this source.
278
+
279
+ ## Installed extensions
280
+
281
+ `@fungi.computer/truffle/extensions` is the one owner of installed extensions.
282
+ It keeps package bytes and the install record in `~/.fungi/extensions`, and the
283
+ activations its own operations write in `~/.fungi/extensions/activations.json`.
284
+ Files in a project or an Agent store never enable anything, so a copied folder
285
+ starts disabled.
286
+
287
+ - `add(source)` installs a Git URL, npm spec or path with the shipped Bun
288
+ package manager, lifecycle scripts disabled and a private cache. A Git source
289
+ is pinned to the commit Bun resolved; updating is another `add`. A package
290
+ whose declared `./host` entry or terminal program is not built is refused.
291
+ - `enableProject(root, id)` turns on the extension's client entry and programs
292
+ for that folder, whichever Agent is selected.
293
+ - `enableAgent(store, id, requester?)` grants one Agent store the capabilities
294
+ the current revision declares. The Agent keeps an older accepted revision
295
+ until it is enabled again, so an upgrade never widens it silently. A human
296
+ (the CLI) names no requester. When an Agent is the requester it must hold the
297
+ extension itself (granted tools, not only its skills), or the call is refused
298
+ with reason `not-held`.
299
+ - `assertHeldByAgent(store, ids)` is that one held-check: it rejects with
300
+ `not-held`, naming every id the Agent lacks. An Agent creating an Agent is
301
+ judged by it before anything exists, so a created Agent never holds more than
302
+ its creator, and a refused request creates nothing.
303
+ - `enableAgentSkills(store, id)` opts one Agent store into the skills folder the
304
+ package declares with `fungi.skills` (for example `"./skills"`), with no tools
305
+ granted; a package with no `./host` can be skills-only. The folder is read in
306
+ place from the installed package, ranks below project and personal skills (so
307
+ yours win), and shows as "from <id>". `disableAgentSkills`, `disableAgent` and
308
+ `remove` take it away. Local installed extensions only; Hub-hosted extensions
309
+ are not covered.
310
+ - `remove(id)` deletes the package and every activation that references it.
311
+
312
+ The package declares itself in `package.json`:
313
+
314
+ ```json
315
+ {
316
+ "exports": { "./host": "./host.mjs" },
317
+ "fungi": {
318
+ "id": "counter",
319
+ "capabilities": ["run_tools"],
320
+ "terminals": [
321
+ { "id": "counter", "name": "Counter", "command": ["./bin/counter.mjs"] }
322
+ ]
323
+ }
324
+ }
325
+ ```
326
+
327
+ `./host` exports a factory. Local calls it with its own `defineExtension`,
328
+ `Mycelium` and `z`, and a 0700 runtime directory for the project. The Agent
329
+ store's roster takes the result when that Agent opens, with exactly the accepted
330
+ capabilities. The same directory reaches the extension's programs as
331
+ `FUNGI_EXTENSION_RUNTIME_DIR`, so a tool can talk to the program a person opened
332
+ in a pane. Installed code is trusted and runs as you.
333
+ [`local-extensions.bun.test.ts`](test/local-extensions.bun.test.ts) installs the
334
+ [counter fixture](test/fixtures/extension-counter/) from a disposable bare Git
335
+ repository and calls its tools through the real workerd `execute`.
336
+
337
+ ## Team connections and ACP
338
+
339
+ A Team MCP connection adds admitted hosted Computer tools to a local Agent. It
340
+ does not move the local Files root or Session storage into the Hub.
341
+
342
+ Local's host-only `presentApproval(sessionId, pending, approvals)` port binds a
343
+ genuine canonical `PendingApproval` and member-bound `Approvals` capability to
344
+ the requesting Session. `mcpFormApprovalContract` uses the existing Session
345
+ extension channel: `pending` contains `{id, approval}`, where `id` is ephemeral
346
+ and `approval` is the canonical `ApprovalView`, including host-computed rights.
347
+ Optional evidence descriptors pass through with that view. They remain untrusted
348
+ display-only metadata, never form input, model input or instructions; this port
349
+ does not fetch or decode images.
350
+
351
+ The Session `answer` command takes `{id, decision, expectedRevision}` with
352
+ `decision: "approve" | "deny"`. Only a live selected-Session UI attachment can
353
+ schedule a decision. After the Session command commits and the attachment is
354
+ rechecked, the client awaits canonical `capability.answer`; that capability
355
+ authorizes the answer and its native host owns durable apply/result delivery.
356
+ The form id, UI attachment and display data cannot grant operation rights. There
357
+ is no second dispatch through MCP `accept`, and no suspended Agent turn. Closing
358
+ or rebinding a UI releases its route, not the durable approval record.
359
+
360
+ This binding is not a TUI renderer or a second ledger. Production submit/apply
361
+ composition still needs the canonical ledger/producer owner and host-attested
362
+ identity, fence and idempotency facts. The legacy boolean MCP form cannot supply
363
+ those facts and is not connected to this Session decision port.
364
+
365
+ **Pending approvals-bundle join:** boorsr owns the canonical ledger/producer
366
+ composition. Local's `observePrivateSqliteOwner(owner, onFailure)` wraps a
367
+ supplied owner without choosing transactions or closing its connection. Local
368
+ selects the actual driver's maintained transaction API: Watchdog's
369
+ `fromNodeSqlite` savepoints for a real Node `DatabaseSync`, and native
370
+ `bun:sqlite` `Database.transaction(...)` for the Bun wrapper. The Node adapter
371
+ is never passed the Bun wrapper; Bun retains native `close(true)` to finalize
372
+ held statements. No savepoint machinery is copied into Local, and the old
373
+ non-nestable `BEGIN` adapter is removed.
374
+
375
+ The same driver laws run through the production opener under Node and Bun:
376
+ nested commit/rollback, native failure classification and cause preservation,
377
+ host-owned connection lifetime, held-statement finalization, and reopen of
378
+ committed data. The canonical submit/get projection tracer also runs against
379
+ both actual Local drivers. Additional approvals-bundle driver tests exercise
380
+ canonical answer/apply, atomic intent rollback and effect replay across reopen
381
+ through unchanged invokeModuleOperation on both drivers. These checks do not
382
+ prove production identity/fences, restart result delivery or producer wiring.
383
+ The form binding and SQLite adapter ride as consumers in boorsr's approvals
384
+ bundle, not as a separate production approval owner.
385
+
386
+ [Fungi account custody](src/private-fungi-account.ts) supplies the same OAuth
387
+ login and locked refresh to MCP and models. `fungi login --provider fungi` opens
388
+ browser sign-in and selects a current Team for model billing. The Local model
389
+ picker refreshes that Team's live hosted catalog. A fresh Local Agent defaults
390
+ to Fungi's catalog default, with `@fungi/luna` as the initial selection before
391
+ the catalog is available. A saved Session override takes precedence; signing in
392
+ does not replace it. The project MCP endpoint and the model's selected Team are
393
+ independent. Switching one does not rewrite the other.
394
+
395
+ `fungi logout` and Pi logout share Local's sign-out owner: it attempts every
396
+ Computer detach before clearing the credential, then retries unconfirmed OAuth
397
+ revocation once. Sign-out always removes local credentials. It names Computers
398
+ whose detach is unconfirmed and links their Hub Computers pages; a running
399
+ backend may still serve until detached there or stopped locally.
400
+
401
+ The private `credentials.json.pending-logouts.json` journal retains only issuer,
402
+ account, login-family, Team, Computer and attach-request IDs plus cleanup
403
+ status, never keys, tokens or folder paths. After the next successful
404
+ same-account sign-in, Local retries cleanup with the new session before Team
405
+ selection. Another account or issuer discards pending intents. The journal
406
+ evicts oldest entries above 128 records or 256 KiB with a diagnostic. There is
407
+ no startup prompt; Computers and Connected logins provide manual recovery. See
408
+ the current
409
+ [Computer backend custody](../../wiki/2-areas/computer-execution-backends.md).
410
+
411
+ [The Fungi provider](src/private-fungi-model-provider.ts) uses Pi's maintained
412
+ completion API through the selected Team. It validates the hosted catalog and
413
+ does not expose supplier credentials to Local. Sign-in, credit, paused-service
414
+ and reachability refusals remain actionable failures. They do not trigger a
415
+ supplier fallback. Local key/subscription providers remain available separately.
416
+
417
+ Attaching a local folder to a Team Computer is a separate dial-out connection
418
+ with its own device key, sync and confirmation. It runs commands as the member's
419
+ OS user. Offline conflicts keep the Computer's version. The absolute folder path
420
+ and private key stay local.
421
+
422
+ [The ACP stdio adapter](src/private-local-acp-stdio.ts) exports
423
+ `runPrivateLocalAcpStdio` through `@fungi.computer/truffle/acp-stdio`. It serves
424
+ stable ACP v1 over the maintained NDJSON stream and the persistent service's
425
+ Sessions. The CLI launches this adapter with `fungi acp`; editor configuration
426
+ uses executable `fungi` and arguments `["acp"]`. It supports text and image
427
+ prompts, embedded text/image resources and resource links, plus Session listing,
428
+ loading and cancellation. Terminal-auth clients invoke `fungi acp login` for the
429
+ ordinary Fungi login. Nonempty additional directories and supplied MCP servers
430
+ are rejected. The admitted directory owns authorization, and closing a client
431
+ lease retains durable Session data.
432
+
433
+ ## Check this runtime
434
+
435
+ From the repository root:
436
+
437
+ ```sh
438
+ pnpm --filter @fungi.computer/truffle typecheck
439
+ pnpm --filter @fungi.computer/truffle lint
440
+ pnpm --filter @fungi.computer/truffle test:full
441
+ ```
442
+
443
+ [Runtime-host tests](test/private-local-runtime-host.test.ts) exercise native
444
+ SQLite, provider fixtures, instruction snapshots, replay and close. On Linux
445
+ with Bun, `pnpm --dir runtimes/local run proof:sqlite-close` also checks
446
+ `/proc/self/fd` immediately after closing an Agent, its create receipt and a
447
+ service lease, without requesting garbage collection. This native descriptor
448
+ proof is Linux-specific; the ordinary Node suite also checks the writable
449
+ adapter's timeout, foreign keys, transaction state and changed rows.
450
+ [Computer tests](test/private-invocation-computer.test.ts) cover process-group
451
+ cleanup and deadlines.
452
+ [Background-process tests](test/private-local-computer-processes.test.ts) cover
453
+ bounded waits and output tails.
454
+ [ACP stdio tests](test/private-local-acp-stdio.test.ts) cover protocol
455
+ initialization. [Fungi model tests](test/private-fungi-model-provider.test.ts)
456
+ cover the live catalog contract, selected-Team billing, streamed fixture replies
457
+ and refusal recovery. [Account tests](test/private-fungi-account.test.ts) cover
458
+ shared refresh and sign-out custody, including competing processes. Bun service
459
+ tests and separate scripts are listed in [package.json](package.json).
460
+ Controlled replies do not prove a live provider login, a published CLI release
461
+ or a hosted Team deployment.
462
+
463
+ ## Selected-Team App reads
464
+
465
+ `openLocalTeamApps` from `@fungi.computer/truffle/apps` opens the saved
466
+ account's selected Team. Its `read`, `requestGill`, `surfaceUrl` and
467
+ `openSession` operations keep credentials inside Local and recheck account
468
+ selection around reads. Gill requests resolve current directory selectors and
469
+ declared actions through the ordinary admitted App door. Hosted Session
470
+ transport uses the Local OAuth door and the same Team/resource capability rule
471
+ as browser Sessions. Closing a Session client releases observation; it does not
472
+ cancel durable work.
473
+
474
+ The directory grammar lives in `@fungi.computer/nacre/app-directory`. UI handoff
475
+ resolves the selected App's published surface on the saved Hub origin, without
476
+ putting an access token in the URL. A later account/Team selection retires this
477
+ client; open a new client for the new selection.
478
+
479
+ ## Subscription credential activation
480
+
481
+ `fungi account use-credential --provider anthropic|openai --from-file -` accepts
482
+ bounded JSON on stdin:
483
+ `{ "expected": <OAuth credential or null>, "credential": <replacement OAuth credential> }`.
484
+ Tokens never travel in arguments or replies. Under the credential store's
485
+ existing refresh lock, the owner compares the expected provider entry
486
+ semantically, preserves every other provider, and atomically persists a 0600
487
+ replacement. A stale comparison fails closed with the fixed `conflict` category.
488
+ Input and filesystem failures expose only `invalid-input` or `store-failure`,
489
+ never parser diagnostics or causes. The machine-readable success is
490
+ `{"ok":true}`; failure also writes a bounded
491
+ `{"ok":false,"error":{"category":"..."}}` reply and exits nonzero.
492
+
493
+ Fungi remains the credential refresher. Local pins the Anthropic/OpenAI account
494
+ at the accepted turn boundary, including all subsequent tool iterations. Pi
495
+ refreshes that same account under the existing store lock while it remains
496
+ active; refreshed tokens do not change the pinned identity. The next turn picks
497
+ up an activated account without restarting the service.
498
+
499
+ Pi's `openai` provider uses the ChatGPT direct-token flow, not the legacy
500
+ `openai-codex` flow. Its credentials contain `clientId` and `scopes`, but do not
501
+ add `accountId`. Local decodes the access token with `jose`; only its issuer and
502
+ person `sub` identify the OpenAI subscription. Same-person refreshes are
503
+ adopted; a different issuer or subject is not. Workspace metadata is not
504
+ identity. An OpenAI grant without a provable person cannot establish turn
505
+ continuity. An already-started refresh is committed under Pi's existing lock
506
+ before Local decides whether the turn can adopt it; refusal or cancellation must
507
+ not discard the rotated refresh token or restore a released pin.
508
+
509
+ After switching A to B, the current turn may continue using A's still-valid
510
+ token, but never refreshes inactive A. If A expires, its next request ends the
511
+ turn with the typed, redacted `account-switched-during-turn` refusal: retry on
512
+ the next turn using B. Completed tool results remain in the Session. A's durable
513
+ inactive credential belongs to the external vault; Local creates no second
514
+ inactive-credential authority and does not rotate it behind the vault's back.
515
+
516
+ For vault capture, `fungi account prepare-credential --provider anthropic`
517
+ enriches a fresh Fungi login with Anthropic's OAuth profile account UUID. OpenAI
518
+ uses unpatched Pi OAuth: Local neither intercepts the code exchange nor reads
519
+ its ID token. After Pi returns a grant, Local requires a usable issuer and
520
+ subject in the access token before storage. No OpenAI workspace `accountId` is
521
+ captured or retained across refresh. Anthropic still retains its profile
522
+ account. `fungi account prepare-credential --provider openai` confirms only the
523
+ access token's issuer and subject, without a request or token change. Failures
524
+ expose `identity-unavailable` and bounded claim names, never token or claim
525
+ values, and preserve the prior entry. A turn detects a different OpenAI person
526
+ by issuer and subject and refuses as `account-switched-during-turn`.
527
+
528
+ Auth tests inject a fixture ChatGPT OAuth provider through `createPrivateModels`
529
+ and pass the resulting collection to `runLocalAuthCli`. They do not start Pi's
530
+ callback server or bind an OAuth port. Explicit temporary credential paths and
531
+ real-file size/mtime guards prevent touching the person's credentials. After
532
+ building Truffle's dependencies,
533
+ `pnpm --dir runtimes/local proof:unpatched-login` packs Truffle with npm and
534
+ signs in through that fixture in an external npm consumer using registry pi-ai
535
+ 1.3.0. This qualifies the local seam and stored person identity, not a live
536
+ OpenAI authorization.
537
+
538
+ A provider HTTP refusal is reported as `provider_rejected` with its status code
539
+ only, never the response body. Use Fungi's ordinary login flow in a private
540
+ temporary HOME for additional accounts. External vault managers must reconcile
541
+ the live credential before switching and treat activation conflicts as failures,
542
+ not overwrite permission. This contract does not authorize a live import, login
543
+ change or cutover.
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Preloaded into the computerd 0.2.1 daemon that Fungi Local starts for an
3
+ * attached folder. computerd binds 0.0.0.0 with no option to change it; its
4
+ * RPC runs commands as this user, so it must never be reachable from the
5
+ * network. Rebind every listener that asks for all interfaces to loopback.
6
+ * Remove this once computerd accepts a listen host.
7
+ */
8
+ "use strict";
9
+ const net = require("node:net");
10
+
11
+ // computerd calls listen(port, "0.0.0.0", callback).
12
+ const listen = net.Server.prototype.listen;
13
+ net.Server.prototype.listen = function loopbackListen(...args) {
14
+ if (args[1] === "0.0.0.0") args[1] = "127.0.0.1";
15
+ return listen.apply(this, args);
16
+ };
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ export { linearAuthInteraction } from "./linear-auth.js";
3
+ import type { AuthInteraction, AuthType, Models } from "@earendil-works/pi-ai";
4
+ export { maximumCredentialStringLength as localAuthInputLimit } from "./private-file-credential-store.js";
5
+ export type LocalAuthModels = Pick<Models, "checkAuth" | "getProviders" | "login" | "logout">;
6
+ /** Typed provider configuration returned to local status callers. */
7
+ export type LocalAuthStatus = Readonly<{
8
+ providerId: string;
9
+ providerName: string;
10
+ state: "unconfigured";
11
+ }> | Readonly<{
12
+ providerId: string;
13
+ providerName: string;
14
+ state: "configured";
15
+ type: AuthType;
16
+ }>;
17
+ export type LocalAuthResult = Readonly<{
18
+ kind: "completed";
19
+ }> | Readonly<{
20
+ kind: "status";
21
+ providers: readonly LocalAuthStatus[];
22
+ }>;
23
+ /**
24
+ * A blank API key was entered. Its message is fixed by the type, never
25
+ * provider output or credential material, so it is safe to show verbatim
26
+ * wherever the login surfaces (including across the Local service).
27
+ */
28
+ export declare class LocalAuthRefusal extends Error {
29
+ constructor();
30
+ }
31
+ export type SignalProcess = {
32
+ once(signal: NodeJS.Signals, listener: () => void): void;
33
+ removeListener(signal: NodeJS.Signals, listener: () => void): void;
34
+ };
35
+ export type LocalAuthCliOptions = {
36
+ /** Credential/device custody selected by the host before presenting auth. */
37
+ credentialPath?: string;
38
+ models?: LocalAuthModels;
39
+ input?: NodeJS.ReadableStream;
40
+ output?: NodeJS.WritableStream;
41
+ signals?: SignalProcess;
42
+ interaction?: AuthInteraction;
43
+ /** Stable host identity for Pi's ChatGPT sign-in. Defaults to the local store's. */
44
+ getDeviceId?: () => Promise<string>;
45
+ };
46
+ export declare function runLocalAuthCli(argv: readonly string[], options?: LocalAuthCliOptions): Promise<LocalAuthResult>;