@amaster.ai/mirrorx-device-client 0.0.0-stage → 0.1.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.
- package/LICENSE +172 -0
- package/README.md +487 -2
- package/dist/codebuddy-auth.d.ts +2 -0
- package/dist/codebuddy-auth.d.ts.map +1 -0
- package/dist/codebuddy-auth.js +80 -0
- package/dist/codebuddy-auth.js.map +1 -0
- package/dist/codebuddy-locks.d.ts +12 -0
- package/dist/codebuddy-locks.d.ts.map +1 -0
- package/dist/codebuddy-locks.js +101 -0
- package/dist/codebuddy-locks.js.map +1 -0
- package/dist/codebuddy-redaction.d.ts +3 -0
- package/dist/codebuddy-redaction.d.ts.map +1 -0
- package/dist/codebuddy-redaction.js +31 -0
- package/dist/codebuddy-redaction.js.map +1 -0
- package/dist/codebuddy-runtime.d.ts +82 -0
- package/dist/codebuddy-runtime.d.ts.map +1 -0
- package/dist/codebuddy-runtime.js +285 -0
- package/dist/codebuddy-runtime.js.map +1 -0
- package/dist/codebuddy-session.d.ts +13 -0
- package/dist/codebuddy-session.d.ts.map +1 -0
- package/dist/codebuddy-session.js +73 -0
- package/dist/codebuddy-session.js.map +1 -0
- package/dist/codebuddy-tool-gate.d.ts +4 -0
- package/dist/codebuddy-tool-gate.d.ts.map +1 -0
- package/dist/codebuddy-tool-gate.js +10 -0
- package/dist/codebuddy-tool-gate.js.map +1 -0
- package/dist/codex-runtime.d.ts +6 -0
- package/dist/codex-runtime.d.ts.map +1 -0
- package/dist/codex-runtime.js +24 -0
- package/dist/codex-runtime.js.map +1 -0
- package/dist/computer.d.ts +25 -0
- package/dist/computer.d.ts.map +1 -0
- package/dist/computer.js +161 -0
- package/dist/computer.js.map +1 -0
- package/dist/configuration-lifetime.d.ts +15 -0
- package/dist/configuration-lifetime.d.ts.map +1 -0
- package/dist/configuration-lifetime.js +143 -0
- package/dist/configuration-lifetime.js.map +1 -0
- package/dist/device-id.d.ts +7 -0
- package/dist/device-id.d.ts.map +1 -0
- package/dist/device-id.js +84 -0
- package/dist/device-id.js.map +1 -0
- package/dist/index.d.ts +209 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1459 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +126 -0
- package/dist/main.js.map +1 -0
- package/dist/managed-workspace.d.ts +33 -0
- package/dist/managed-workspace.d.ts.map +1 -0
- package/dist/managed-workspace.js +149 -0
- package/dist/managed-workspace.js.map +1 -0
- package/dist/pi-runtime.d.ts +24 -0
- package/dist/pi-runtime.d.ts.map +1 -0
- package/dist/pi-runtime.js +156 -0
- package/dist/pi-runtime.js.map +1 -0
- package/dist/readiness.d.ts +12 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +66 -0
- package/dist/readiness.js.map +1 -0
- package/dist/resource-lock.d.ts +11 -0
- package/dist/resource-lock.d.ts.map +1 -0
- package/dist/resource-lock.js +74 -0
- package/dist/resource-lock.js.map +1 -0
- package/dist/session-index.d.ts +41 -0
- package/dist/session-index.d.ts.map +1 -0
- package/dist/session-index.js +99 -0
- package/dist/session-index.js.map +1 -0
- package/dist/user-service.d.ts +21 -0
- package/dist/user-service.d.ts.map +1 -0
- package/dist/user-service.js +224 -0
- package/dist/user-service.js.map +1 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,488 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MirrorX device client
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package is a small Node.js bridge for a user-owned Pi, Codex, or WorkBuddy executor.
|
|
4
|
+
It makes outbound requests to MirrorX; the device needs no inbound connection.
|
|
5
|
+
The package has no runtime dependencies on Pi Agent. With
|
|
6
|
+
`MIRRORX_DEVICE_SERVER_URL` set by the Pi distribution, Pi uses its existing
|
|
7
|
+
IAM login to register in every MirrorX company where the linked IAM subject is
|
|
8
|
+
already a member. No second browser approval is needed. The MirrorX server
|
|
9
|
+
uses its existing `IAM_INTERNAL_BASE_URL` to verify the login token through
|
|
10
|
+
IAM userinfo, and `IAM_PUBLIC_BASE_URL` (or `IAM_BASE_URL`) as the issuer used
|
|
11
|
+
by existing IAM account links. No separate device userinfo URL is needed.
|
|
12
|
+
Email is not an identity fallback.
|
|
13
|
+
The Amaster desktop app is the ordinary entry point and carries this library in its own runtime; users do not install Node/npm. In MirrorX Settings → Runtime and integrations, select a connected computer, name the employee and choose Amaster, Codex or WorkBuddy. Automatic managed workspaces are the default and require no second native confirmation. Custom Codex/WorkBuddy project directories retain native confirmation of the exact path; the `amaster://device/connect?intent=UUID` link contains no credential. Login creates/restores only the personal twin. Additional employees consume explicit authorized enrollment intents.
|
|
14
|
+
|
|
15
|
+
The standalone CLI connects a **computer only**. In the web connection dialog,
|
|
16
|
+
generate a one-use computer pairing code, then run `setup --server URL`. No tool,
|
|
17
|
+
workspace, personal twin or other employee is created during pairing. Codes last
|
|
18
|
+
ten minutes and can be generated before a computer is online. Already connected
|
|
19
|
+
computers can be selected directly without another code.
|
|
20
|
+
|
|
21
|
+
## Online-only operation and recovery
|
|
22
|
+
|
|
23
|
+
V1 submits work only while online and ready. Known offline/not-ready submissions retain browser drafts and send no new task/run request. Reconnection refreshes state; the user must submit again. Existing accepted tasks/timers can still fail while the computer sleeps. There is no offline queue or automatic scheduling in this connector.
|
|
24
|
+
|
|
25
|
+
`setup` installs a current-user LaunchAgent, Windows task or systemd user service. It asks before a foreground-only fallback on unsupported systems. `service stop` requests drain through a matching private instance record; uncertain commands remain for reconciliation. Repeated start does not create another owned supervisor, and unknown service ownership is rejected. OS service installations still require real per-platform validation. App background and login-start options are separate from standalone service ownership.
|
|
26
|
+
|
|
27
|
+
Normal restore requires the original owner, safe disconnect and no unsettled commands/leases. Permanent loss is a distinct MirrorX recovery permission with an exact command-set confirmation, immutable receipt and generation fence. It abandons results, never claims the old process stopped, preserves usage and pauses affected task trees until manual action. Restoring retains the original employee/history and starts new native sessions. Lost index repair requires a valid narrow session; completely lost references require an explicit existing-Pi/new-computer choice.
|
|
28
|
+
|
|
29
|
+
Protocol and distribution metadata are available at `/api/devices/compatibility`. Desktop and enabled npm channels have independent exact versions and integrity/provenance checks. Missing published npm does not block the app. Unsupported protocol stops fresh claims/enrollment, retaining identities and allowing authenticated original in-flight claims to settle. Never delete identity files to fix an upgrade requirement.
|
|
30
|
+
|
|
31
|
+
## Standalone installation and updates
|
|
32
|
+
|
|
33
|
+
After the first public release, install this connector without the Pi Agent app:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install -g @amaster.ai/mirrorx-device-client@VERSION
|
|
37
|
+
mirrorx-device --version
|
|
38
|
+
mirrorx-device --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Replace `VERSION` with a published version number.
|
|
42
|
+
Generate a computer pairing code in MirrorX and run the commands below. Install
|
|
43
|
+
and sign in to the chosen tool later, before assigning work to an employee. Node.js22.19+ is required. Update
|
|
44
|
+
by selecting the new version in the same npm command after your own connector's
|
|
45
|
+
tasks finish and the bridge exits; restart it afterward. Publishing a new version never updates or stops
|
|
46
|
+
existing bridge processes automatically. Keep the old version available while
|
|
47
|
+
reconciling any interrupted work.
|
|
48
|
+
|
|
49
|
+
The release-branch/manual npm release workflow, maintainer credential setup, exact-tarball
|
|
50
|
+
native smoke and rollback instructions are in
|
|
51
|
+
[the release runbook](../../docs/releasing-mirrorx-device-client.md). Before the
|
|
52
|
+
first successful publication, maintainers can use the verified tgz attached to
|
|
53
|
+
a dry-run workflow; a new workflow file in a Draft PR is not a published npm version.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
mirrorx-device setup --server https://YOUR-MIRRORX
|
|
57
|
+
mirrorx-device service status
|
|
58
|
+
mirrorx-device doctor --kind codex
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`setup` prompts for the code without putting it in shell history. A narrow
|
|
62
|
+
computer credential is stored in the owner-only identity directory. The connector
|
|
63
|
+
reports presence every 15 seconds with a 60-second fenced lease, even with zero
|
|
64
|
+
employees or while a tool is unready. The supervisor discovers new computer
|
|
65
|
+
connections without requiring an existing service to be replaced.
|
|
66
|
+
|
|
67
|
+
Create employees explicitly in MirrorX after pairing. This package supports
|
|
68
|
+
managed Codex/WorkBuddy workspaces on macOS (Seatbelt confinement); it does not
|
|
69
|
+
bundle the Amaster Pi runtime. Other platforms can connect and report presence,
|
|
70
|
+
but must not claim managed-workspace support. The desktop app supplies Amaster's
|
|
71
|
+
runtime. Tool execution retains its existing journals, approval gates and local
|
|
72
|
+
readiness checks. A computer credential cannot execute tasks directly.
|
|
73
|
+
|
|
74
|
+
Legacy `pair --kind ... --workspace ...` remains an employee-bound maintainer
|
|
75
|
+
command; its codes are deliberately incompatible with computer-only pairing.
|
|
76
|
+
`setup` rejects those legacy flags rather than silently creating an employee.
|
|
77
|
+
Pi sign-out stops only app-owned bridges and clears their narrow credentials, retaining non-secret binding references. Natural expiry/normal rotation may renew the same Pi binding through the current IAM identity. Explicit revocation, membership loss and protocol rejection never authorize automatic re-enrollment.
|
|
78
|
+
|
|
79
|
+
The bridge journals each claim and durably records a launch intent before
|
|
80
|
+
spawning the process. A verified pre-spawn claim can retry a failed start;
|
|
81
|
+
an already launched or older ambiguous journal entry requires reconciliation.
|
|
82
|
+
A private random UUID lives in an OS-user identity directory independent of the overridable task state/install directory. No hardware identifiers are read. An atomic local lock covers canonical server origin, authenticated principal, profile, company and executor; it never signals unknown owners. MirrorX also leases the active bridge
|
|
83
|
+
instance in PostgreSQL and stores the instance that started each command, so
|
|
84
|
+
a copied configuration on another computer cannot start the same command.
|
|
85
|
+
The bridge includes up to 32 locally in-flight command IDs in each poll,
|
|
86
|
+
including commands awaiting output transfer or completion acknowledgement. MirrorX
|
|
87
|
+
can then deliver queued control commands while a long process runs; a command
|
|
88
|
+
omitted from this list or owned by an older bridge still blocks new claims until
|
|
89
|
+
it is reconciled. Older MirrorX servers ignore the optional poll parameter.
|
|
90
|
+
Acknowledged terminal entries are removed from the local journal. The client
|
|
91
|
+
spools output segments under `~/.mirrorx-device/output-outbox/` and uploads
|
|
92
|
+
their bytes directly to S3 using short-lived URLs while the Run is active.
|
|
93
|
+
PostgreSQL keeps the segment cursor and transfer metadata; the plugin verifies
|
|
94
|
+
each segment before forwarding it to the Run log. A slow upload temporarily pauses
|
|
95
|
+
stdout/stderr reads; if transfer remains unavailable, the process continues
|
|
96
|
+
and output stays in the local outbox for reconnect. There is no task-wide
|
|
97
|
+
output byte limit or output-volume kill switch. If the
|
|
98
|
+
process finished before an acknowledgement was lost, the bridge replays its
|
|
99
|
+
outbox on reconnect. A process crash with uncertain side effects still needs
|
|
100
|
+
reconciliation. If local output staging fails, the bridge stops the process and
|
|
101
|
+
reports a failed command with an explicit output-loss message; it cannot safely
|
|
102
|
+
replay missing bytes. On macOS and Linux, individual stop requests signal the
|
|
103
|
+
managed process group, then escalate to SIGKILL after three seconds if any
|
|
104
|
+
member remains and the root is still owned. If the root exits before escalation,
|
|
105
|
+
the bridge observes for a bounded grace, then requires reconciliation; it cannot
|
|
106
|
+
safely signal an unowned/reused PID. On Windows, the bridge uses `taskkill /T /F` for the process
|
|
107
|
+
tree. A stop is not reported complete until that operation has finished; a
|
|
108
|
+
failed tree stop leaves the command for reconciliation. If the Run
|
|
109
|
+
controller is interrupted, the device keeps running
|
|
110
|
+
and MirrorX acknowledges later ordinary log segments without uploading them.
|
|
111
|
+
The final command result carries a summary budgeted in UTF-8 bytes across both
|
|
112
|
+
output streams so the whole terminal request stays below the host's one-megabyte
|
|
113
|
+
limit. The device plugin removes
|
|
114
|
+
forwarded temporary objects and expires orphaned output after a short grace
|
|
115
|
+
period; it does not keep a second full stdout archive. The host's live log
|
|
116
|
+
notification is best effort and has no durable acknowledgement. Real multi-Pod/OSS
|
|
117
|
+
validation remains before production use.
|
|
118
|
+
|
|
119
|
+
Pi Billing commands use the device's configured model catalog together with
|
|
120
|
+
the per-run routing supplied by MirrorX. The Pi server passes its effective
|
|
121
|
+
configuration directory and default model to the bridge. Standalone Pi bridges
|
|
122
|
+
use `PI_CODING_AGENT_DIR`, falling back to `~/.pi/agent`; that directory must
|
|
123
|
+
contain a configured `amaster` model catalog. A routing-only provider is not a
|
|
124
|
+
catalog. The bridge builds a private, disposable command configuration, retains
|
|
125
|
+
explicit model selection, and only fills an omitted model from the device's
|
|
126
|
+
configured default. Local provider credentials and model-level routing are
|
|
127
|
+
excluded from the merge so the organization Billing lease remains authoritative.
|
|
128
|
+
The user's configuration and the server-staged files are left unchanged.
|
|
129
|
+
|
|
130
|
+
For Billing commands, an omitted model can reuse the configured default model ID
|
|
131
|
+
from another provider only when that ID exists in the `amaster` Billing catalog.
|
|
132
|
+
An unmatched default is an explicit configuration failure, not an arbitrary model
|
|
133
|
+
selection. Missing values for `--model`, `-m`, or `--provider` are rejected before
|
|
134
|
+
injection. Only `models.json` and staged `settings.json` are materialized; caches,
|
|
135
|
+
sessions, local OAuth/authHeader settings and provider-level compat are not copied.
|
|
136
|
+
The injected Billing authentication remains authoritative. Preparation retries
|
|
137
|
+
filesystem resource errors at most three times before launching a process;
|
|
138
|
+
deterministic invalid configuration is not retried.
|
|
139
|
+
|
|
140
|
+
On POSIX, a Billing command remains active while observing managed descendants
|
|
141
|
+
for up to 30 seconds starting at the root's `exit` event. Root exit and stdio
|
|
142
|
+
`close` are tracked separately: descendants inheriting stdout/stderr cannot keep
|
|
143
|
+
the observation deadline or abort cleanup from running. Normal completion still
|
|
144
|
+
waits for output EOF within that deadline. Unconfirmed completion closes only
|
|
145
|
+
local pipe handles, retains the running journal without ACK, and applies the
|
|
146
|
+
platform cleanup policy below. Once the root has exited, this is strictly
|
|
147
|
+
observation: no new stop signal is sent to that PID/group, including on abort or
|
|
148
|
+
deadline. An already-started stop may escalate only while the root is still live;
|
|
149
|
+
otherwise it also switches to observation/reconciliation. This trades automatic
|
|
150
|
+
termination of surviving tools for protection against signalling a reused PID.
|
|
151
|
+
Stop/reap observation is bounded (three-second TERM and KILL grace, followed by
|
|
152
|
+
at most five seconds of reap grace; abort skips the extra reap wait).
|
|
153
|
+
|
|
154
|
+
If exit cannot be confirmed, no completion ACK is sent; the running journal
|
|
155
|
+
requires manual reconciliation and a replacement bridge refuses to relaunch it.
|
|
156
|
+
Execution timers/active ownership are cleared and shutdown releases its bridge lock.
|
|
157
|
+
Unconfirmed execution retains its private Billing directory on both POSIX and
|
|
158
|
+
Windows; the running journal records `configurationDirectory` and an ownership
|
|
159
|
+
token, never the credential contents.
|
|
160
|
+
A surviving tool may still read those files. Retention does not extend the
|
|
161
|
+
server's Billing lease or establish successful completion. Normal completion
|
|
162
|
+
persists cleanup evidence and removes private configuration before replacing the
|
|
163
|
+
running record or sending the completion ACK. Startup and subsequent polls scan
|
|
164
|
+
all journal configuration references, even if the server no longer returns the command.
|
|
165
|
+
POSIX reconciliation keeps passive, unreferenced pipe readers: actual stdout and
|
|
166
|
+
stderr EOF after root exit is persisted in a private ownership marker. Once the
|
|
167
|
+
original process group is also absent, a live desktop reclaims the directory;
|
|
168
|
+
a later bridge can consume the same evidence. Local stream destruction, EPERM,
|
|
169
|
+
an elapsed timeout or PID reuse never establishes exit. These readers/timers do
|
|
170
|
+
not keep a closing CLI alive and send no process signals. Windows uses
|
|
171
|
+
a foreground-command contract: tools must not outlive a normally completed Pi
|
|
172
|
+
root. Normal root close is not evidence of native Windows job/tree containment.
|
|
173
|
+
If the application exits before observing EOF, an old journal has no ownership
|
|
174
|
+
evidence, or a Windows tree stop fails, automatic reclamation cannot prove safety.
|
|
175
|
+
After stopping the bridge and confirming **all tools for that command stopped**, run:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
mirrorx-device reclaim-config --kind pi --company COMPANY_ID --command COMMAND_ID --confirm-stopped
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This local recovery command consumes only that journal entry's private temporary
|
|
182
|
+
directory; it validates the canonical absolute path, location/ownership and token when present,
|
|
183
|
+
refuses an active company/executor bridge lock, and preserves the running state
|
|
184
|
+
and claim for reconciliation. It never acknowledges or relaunches the command.
|
|
185
|
+
It also supports pre-token retained entries. No global `/tmp` sweep is performed.
|
|
186
|
+
Escaped process groups are outside managed ownership. Real Windows runtime
|
|
187
|
+
behavior still needs platform E2E verification.
|
|
188
|
+
|
|
189
|
+
Only the MirrorX managed Billing profile is supported here: its producer writes
|
|
190
|
+
models.json/settings.json, not auth.json, local extensions, or provider state.
|
|
191
|
+
This restriction applies to the command's server-staged `PI_CODING_AGENT_DIR`,
|
|
192
|
+
not the device's `PiDeviceRuntime.configDir`. Extra files, credential caches and
|
|
193
|
+
MCP configuration in the local profile do not reject a command or get copied.
|
|
194
|
+
Any additional staged entry is rejected before launch, regardless of which current
|
|
195
|
+
or future settings key references it. The bridge does not classify Pi package
|
|
196
|
+
sources or interpret resource settings; Pi owns those semantics. Arbitrary Pi
|
|
197
|
+
profiles require a separate resource-transfer contract; they are not cloned.
|
|
198
|
+
Missing settings.json stays absent in the command directory.
|
|
199
|
+
EACCES and ENOSPC are reported immediately; transient filesystem failures retain the
|
|
200
|
+
bounded retry. Materialized files are created mode0600 in a new private directory.
|
|
201
|
+
|
|
202
|
+
When the supplied default model ID is absent, the bridge also checks the device's
|
|
203
|
+
explicit settings default against the merged catalog. If neither is available,
|
|
204
|
+
preparation fails with a configuration error; it does not silently select an
|
|
205
|
+
arbitrary model. Injected provider `api` takes precedence over the local fallback
|
|
206
|
+
because it describes the Billing endpoint protocol. Provider-level `compat` is
|
|
207
|
+
not a supported Pi field; supported model-level `compat` is merged.
|
|
208
|
+
|
|
209
|
+
A catalog-declared dash-prefixed model can be passed as `--model -odd-id`;
|
|
210
|
+
the bridge normalizes `-m`, model/provider equals syntax to Pi's native
|
|
211
|
+
`--model value` / `--provider value` form. Repeated options keep the last value;
|
|
212
|
+
arguments after `--` stay unchanged. Informational commands leave
|
|
213
|
+
model/provider option validation to Pi and do not inject defaults.
|
|
214
|
+
Malformed staged settings retain the filename and `invalid JSON` diagnosis without
|
|
215
|
+
including JSON content or credentials.
|
|
216
|
+
|
|
217
|
+
Stop confirmation has separate platform paths in one completion-state helper.
|
|
218
|
+
An unconfirmed stop emits one reconciliation notice including its failure reason.
|
|
219
|
+
Windows accepts only its own stop-task outcome: a failed/pending stop after root
|
|
220
|
+
exit requires reconciliation and retains configuration, never a POSIX negative-PID
|
|
221
|
+
probe to infer success. POSIX alone performs bounded group observation/reaping.
|
|
222
|
+
`active` owns live execution; `inFlight` continues through output/ACK transfer.
|
|
223
|
+
A late stop during that transfer must not signal a reaped process. Root success
|
|
224
|
+
while owned tools remain alive is not full-command completion; reconciliation
|
|
225
|
+
remains required after the observation deadline. Help/version/list commands do
|
|
226
|
+
not start model execution; ordinary execution receives the validated device model.
|
|
227
|
+
Provider-level compat is stripped from both local and injected input, while
|
|
228
|
+
supported model-level compat from both sources remains merged.
|
|
229
|
+
|
|
230
|
+
### Embedded desktop runtime
|
|
231
|
+
|
|
232
|
+
Desktop passes its app-owned Node and Pi launcher via `PiDeviceRuntime.executable`
|
|
233
|
+
for recognized `pi` / `pi.cmd` / `pi.exe` commands, including path forms. No global Pi installation or shell
|
|
234
|
+
interpolation is needed. Other commands, including the existing Codex bridge,
|
|
235
|
+
retain their configured executable. Desktop restores only the current IAM
|
|
236
|
+
subject, newly enrolled companies and configured service (`expectedBaseUrl`).
|
|
237
|
+
A failed authenticated enrollment does not reactivate stale stored companies.
|
|
238
|
+
Transport errors, timeouts, HTTP 408/429 and 5xx responses retry automatically
|
|
239
|
+
with exponential delay from one second up to 30 seconds. Sign-out or a new
|
|
240
|
+
identity cancels the pending retry; successful empty enumeration and permanent
|
|
241
|
+
authentication/configuration failures do not restore old pairings or retry.
|
|
242
|
+
A 200 response with only transient company-registration failures also retries.
|
|
243
|
+
Once any company succeeds, an unsuccessful subset does not restart the whole
|
|
244
|
+
bridge set; the client's bounded per-company retries remain in effect.
|
|
245
|
+
|
|
246
|
+
`MIRRORX_DEVICE_HOME` optionally selects the credential/journal root. Desktop
|
|
247
|
+
sets this to its profile's system directory; the standalone CLI default remains
|
|
248
|
+
`~/.mirrorx-device`. Keep different services/profiles in separate roots. This
|
|
249
|
+
override does not migrate or erase existing state.
|
|
250
|
+
|
|
251
|
+
### Application-owned company recovery
|
|
252
|
+
|
|
253
|
+
`enrollMirrorxDevice` accepts optional `companyIds` and `signal`. Scoped requests
|
|
254
|
+
register only those companies, so recovery cannot rotate healthy companies'
|
|
255
|
+
sessions. Cancellation reaches HTTP requests and short retry delays. Device
|
|
256
|
+
RPC 401/403 terminates the affected loop after draining owned execution and
|
|
257
|
+
releasing its bridge lock; other companies keep running. Network failures retain
|
|
258
|
+
the existing 3s reconnect loop. `onConnectionState` reports actual connection and
|
|
259
|
+
retry/failure transitions, separately from command reconciliation diagnostics.
|
|
260
|
+
|
|
261
|
+
The embedded lifecycle holds one bridge per company, retries transient failed
|
|
262
|
+
registrations with 1s–30s backoff and refreshes same-identity IAM credentials.
|
|
263
|
+
Its existing HTTP status/reconnect entry includes device health and recovers
|
|
264
|
+
failed companies without stopping healthy ones. Normal application quit aborts
|
|
265
|
+
bridges and pending registration/retries; this is not a persistent daemon. Old
|
|
266
|
+
Employee twin identities, tasks and workspaces are not migrated or adopted, and
|
|
267
|
+
legacy PID/status files do not gate the new device connection.
|
|
268
|
+
|
|
269
|
+
## WorkBuddy on macOS (experimental)
|
|
270
|
+
|
|
271
|
+
Install and sign in to WorkBuddy first, then select **WorkBuddy (macOS)** in
|
|
272
|
+
MirrorX Profile → Connect a device. Pair and run this same standalone client:
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
mirrorx-device pair --server https://your-mirrorx.example --kind codebuddy --code YOUR_CODE --workspace "/path/to/workspace"
|
|
276
|
+
mirrorx-device run --kind codebuddy
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Requires Node 22.19+ and the server's built-in `codebuddy_local` adapter
|
|
280
|
+
(`@paperclipai/adapter-workbuddy-local`, registered like Codex and Pi).
|
|
281
|
+
WorkBuddy is another executor kind in this same lightweight connector; no
|
|
282
|
+
separate WorkBuddy connector package or external server adapter installation
|
|
283
|
+
is required. The client uses
|
|
284
|
+
WorkBuddy's built-in `/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy`;
|
|
285
|
+
a different executable may be selected **locally** with `MIRRORX_WORKBUDDY_CLI`.
|
|
286
|
+
The server cannot select a desktop-auth source directory. Windows desktop-auth
|
|
287
|
+
reuse is refused until native ACL and stop qualification exists. No API-key
|
|
288
|
+
fallback, automatic WorkBuddy installation, or account migration is provided.
|
|
289
|
+
|
|
290
|
+
Only actual `codebuddy` launches receive a private HOME/config and an opaque
|
|
291
|
+
copy of the OS user's encrypted desktop-auth file. The source is never decrypted,
|
|
292
|
+
logged, uploaded, or written back. A logout marker, symlink, unsafe ownership,
|
|
293
|
+
permissions, or changing file identity refuses startup. Each launch checks CLI
|
|
294
|
+
flags and current auth presence; a previous model success is not permanent login
|
|
295
|
+
proof. Vendor keys, tokens, base URLs, NODE_OPTIONS, and personal configuration
|
|
296
|
+
are excluded by rebuilding the final child environment. Local HTTP_PROXY,
|
|
297
|
+
HTTPS_PROXY, ALL_PROXY and lowercase equivalents are retained and redacted before
|
|
298
|
+
output spooling. Pi/Codex retain their existing environment behavior.
|
|
299
|
+
|
|
300
|
+
Workspaces containing device state, desktop auth, the selected CLI, or run-private
|
|
301
|
+
configuration are refused. The qualified profile exposes adapter-owned workspace file MCP tools and the current run's exact
|
|
302
|
+
authorized MCP tools. Native Read/Write/Edit/Glob/Grep are disabled after a real probe showed Read can access a workspace-external canary. File operations go through the adapter MCP with path/link and size checks. Bash, Skills, subagents, personal MCP, cron and desktop
|
|
303
|
+
features are unavailable. Every tool call also passes a private `PreToolUse`
|
|
304
|
+
positive gate: only this run's exact MCP names are allowed, including rejection
|
|
305
|
+
of unknown native tools or MCP names. `dontAsk` prevents approval prompts; it
|
|
306
|
+
is insufficient on its own because safe native reads can be auto-approved.
|
|
307
|
+
The explicit native deny list remains defense in depth and keeps these tools
|
|
308
|
+
out of the advertised surface; `--tools` alone does not hide them. Workspaces
|
|
309
|
+
must not overlap device state, the desktop-auth tree, or the WorkBuddy app bundle
|
|
310
|
+
in either direction. Model selection uses CodeBuddy's native account
|
|
311
|
+
default unless the employee explicitly selects a model; this mode does not
|
|
312
|
+
use MirrorX Billing. Usage is recorded when supplied; monetary cost is unknown.
|
|
313
|
+
|
|
314
|
+
Session transcripts stay in the client state directory, outside the workspace,
|
|
315
|
+
scoped by service/company/employee/binding/real cwd/tool authorization. The
|
|
316
|
+
connector saves a successful native transcript **before** deleting private auth
|
|
317
|
+
and MCP configuration, then restores it before the next spawn. Only the selected
|
|
318
|
+
transcript is saved; no auth or MCP credential files are copied into session
|
|
319
|
+
storage. A transcript containing current MCP/proxy credential values is rejected.
|
|
320
|
+
Both a workspace lock and a session lock reject overlap. An unsettled attempt fences the previous
|
|
321
|
+
checkpoint rather than silently resuming stale state. CodeBuddy 2.137.1 receives
|
|
322
|
+
new/resume prompts as positional arguments because stdin-only resume did not
|
|
323
|
+
process the new prompt in the real contract probe.
|
|
324
|
+
|
|
325
|
+
WorkBuddy 2.137.1 encodes the launch directory into one native session directory
|
|
326
|
+
component. For long managed task paths, the connector launches in a short,
|
|
327
|
+
command-private directory instead. MCP tools still use the original authorized
|
|
328
|
+
task workspace; session identity and workspace locks keep that original path.
|
|
329
|
+
Only the WorkBuddy executor can request this private launch directory. The
|
|
330
|
+
sandbox still denies neighboring task workspaces. Checkpoints are collected
|
|
331
|
+
from, and restored to, the actual private native-session bucket.
|
|
332
|
+
|
|
333
|
+
Preparation records private-profile ownership in the command journal before
|
|
334
|
+
acquiring workspace/session locks or reading authentication. Lock publication
|
|
335
|
+
includes its owner atomically. A preparation crash retains this command-scoped
|
|
336
|
+
recovery reference: stop the bridge and use `reclaim-config --kind codebuddy`
|
|
337
|
+
with confirmed-stop evidence before retrying. Managed bindings must also pass
|
|
338
|
+
`--company COMPANY_ID --binding BINDING_ID --command COMMAND_ID --confirm-stopped`
|
|
339
|
+
so reclamation selects and locks the exact binding journal. An unknown binding
|
|
340
|
+
fails rather than falling back to another connection. A retry never overwrites the
|
|
341
|
+
retained reference. If preparation was explicitly cancelled before any business
|
|
342
|
+
process started, the bridge rolls back only that preparation's fence and keeps
|
|
343
|
+
the previous successful checkpoint, so resume does not require `reset-session`.
|
|
344
|
+
|
|
345
|
+
Cancellation uses the existing owned process-tree contract, not WorkBuddy GUI
|
|
346
|
+
shutdown. After verified shutdown, a CLI that handles the stop signal and exits
|
|
347
|
+
zero is still acknowledged as stopped; it is not required to emit a successful
|
|
348
|
+
provider result or create a success checkpoint. Unknown descendant shutdown
|
|
349
|
+
continues to require reconciliation. Unconfirmed exit or failed transcript persistence retains the running
|
|
350
|
+
journal/profile and requires reconciliation; it is never replayed. CodeBuddy
|
|
351
|
+
profiles with incomplete shutdown/session evidence require explicit `reclaim-config`
|
|
352
|
+
after independently confirming all tools stopped. Reclamation does not turn an
|
|
353
|
+
unsettled native checkpoint into a resumable session.
|
|
354
|
+
|
|
355
|
+
The protocol readiness check captures help with a private regular file descriptor:
|
|
356
|
+
CodeBuddy 2.137.1 may exit before pipe output drains and omit help tail flags.
|
|
357
|
+
A successful version check alone is insufficient. Only version **2.137.1** is
|
|
358
|
+
qualified; other versions fail before task launch even if their help flags
|
|
359
|
+
match. Qualify the permission contract again before updating this pin.
|
|
360
|
+
See CodeBuddy's [PreToolUse contract](https://www.codebuddy.cn/docs/cli/hooks)
|
|
361
|
+
and [permission-mode semantics](https://www.codebuddy.cn/docs/cli/permission-modes).
|
|
362
|
+
The CLI remains trusted local software; these controls are not an OS sandbox.
|
|
363
|
+
|
|
364
|
+
A clean process exit requires exactly one matching successful result before a
|
|
365
|
+
checkpoint can be saved. Exactly one matching known error result (including
|
|
366
|
+
`error_during_execution` with `is_error: true`) is a confirmed failed command,
|
|
367
|
+
even when CodeBuddy exits zero. After confirming the process tree stopped, the
|
|
368
|
+
bridge cleans its private launch configuration and acknowledges failure. It does
|
|
369
|
+
not checkpoint or replay that failed native session: the unsettled session fence
|
|
370
|
+
remains. Missing, malformed, unknown or duplicate results still require command
|
|
371
|
+
reconciliation; exit zero alone cannot ACK success. A provider 401 needs normal
|
|
372
|
+
WorkBuddy account reauthentication and is never repaired by relabeling the run.
|
|
373
|
+
|
|
374
|
+
### Discard a fenced native session
|
|
375
|
+
|
|
376
|
+
First reconcile the original command in MirrorX and independently confirm its
|
|
377
|
+
tools stopped. Stop this company's bridge. If a private configuration remains,
|
|
378
|
+
run the existing `reclaim-config --kind codebuddy` operation first. Reclaiming
|
|
379
|
+
configuration alone deliberately leaves the native-session fence in place.
|
|
380
|
+
The fenced-session error supplies `SESSION_KEY` and `SESSION_ID`:
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
mirrorx-device reset-session --kind codebuddy --company COMPANY_ID \
|
|
384
|
+
--session-key SESSION_KEY --session SESSION_ID --confirm-stopped
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
This explicit reset takes the company bridge and workspace/session locks,
|
|
388
|
+
validates the stored company/identity, and deletes the old checkpoint **before**
|
|
389
|
+
removing the fence. It neither changes the command journal/receipt nor retries
|
|
390
|
+
business work. Resume of the discarded context fails; start a fresh session.
|
|
391
|
+
Existing fenced sessions acquire their recovery identity when preparation is
|
|
392
|
+
retried; do not manually delete state or locks to bypass ownership checks.
|
|
393
|
+
|
|
394
|
+
New WorkBuddy pairings/IAM enrollments default to the sibling
|
|
395
|
+
`<MIRRORX_DEVICE_HOME>-workspaces/<company>/codebuddy` directory (normally
|
|
396
|
+
`~/.mirrorx-device-workspaces/...`). Pi/Codex defaults stay inside their existing
|
|
397
|
+
workspaces directory. No files are moved. If an older WorkBuddy config selects
|
|
398
|
+
a workspace inside the state/auth tree, pair again with an explicit safe
|
|
399
|
+
`--workspace` and move your project files deliberately; the launch guard will
|
|
400
|
+
not weaken itself to accept the old location.
|
|
401
|
+
|
|
402
|
+
### Managed desktop workspaces
|
|
403
|
+
|
|
404
|
+
In MirrorX **运行与集成**, select an online Amaster computer, choose Amaster, Codex or
|
|
405
|
+
WorkBuddy, and name the employee. Additional Amaster employees require the desktop
|
|
406
|
+
carrier to advertise `managedPiEmployees`; older clients show an upgrade requirement. A compatible desktop consumes
|
|
407
|
+
only the signed intent addressed to its authenticated company/profile; no local
|
|
408
|
+
directory picker or second per-employee connection prompt is needed. Login alone
|
|
409
|
+
still connects only the personal twin. Custom project directories retain native confirmation.
|
|
410
|
+
|
|
411
|
+
The desktop derives its workspace namespace from the canonical service,
|
|
412
|
+
principal, profile, company and enrollment identity. Each employee has its own
|
|
413
|
+
binding/config/journal/lock. The environment provider reuses core execution
|
|
414
|
+
workspace IDs (or the exact task ID) for stable task directories and continuation.
|
|
415
|
+
Managed commands validate ownership and real paths before spawn and use native
|
|
416
|
+
filesystem confinement. Current managed qualification is macOS Seatbelt; other
|
|
417
|
+
platforms fail closed and can use explicitly authorized custom workspaces.
|
|
418
|
+
Existing custom bindings keep their paths and legacy resource lock keys.
|
|
419
|
+
|
|
420
|
+
Native Pi/Codex session files are indexed by exact references under the binding's
|
|
421
|
+
private state. History lookup validates the login, service, company, employee and
|
|
422
|
+
source Run, and does not scan other employee workspaces or recreate legacy daemon
|
|
423
|
+
markers. Task attachment, tool/MCP and artifact delivery still use the existing
|
|
424
|
+
MirrorX adapter and task APIs. Connectivity remains optional and appears in Pi
|
|
425
|
+
Settings → MirrorX.
|
|
426
|
+
|
|
427
|
+
Codex cancellation first sends Ctrl-C (`SIGINT`) with a bounded grace period so Codex can interrupt its active turn and stop tool process groups it owns. It then uses the existing termination fallback while the command root is still owned. A forced Codex stop remains unconfirmed and requires reconciliation rather than acknowledging that detached tools have stopped. Pi/WorkBuddy and Windows retain their existing stop paths; a reaped root never authorizes signaling a reused PID.
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
### Desktop sign-in restoration
|
|
431
|
+
|
|
432
|
+
After logout, Pi retains the original tool binding and workspace but clears its credential. On sign-in it first reconnects Pi, then refreshes only previously authorized desktop tool sessions on the same account, company, profile and generation. No employee or workspace is recreated. The server rejects explicit revocation, profile transfers, active bridges and unsettled execution. Standalone connectors are not enrolled or restarted by this flow. Tool failures appear in Pi Settings → MirrorX and do not block normal Pi conversations.
|
|
433
|
+
|
|
434
|
+
Local credential clearing precedes the remote sign-out request, including when a committed remote response is lost. Retained stale credentials may recover only from `DEVICE_SESSION_EXPIRED`, `DEVICE_SESSION_ROTATED`, or `DEVICE_SESSION_SIGNED_OUT` through the authenticated Pi session; `DEVICE_REVOKED` remains terminal. An idle bridge disconnect includes its company and exact instance ID so the server can release only that bridge's lease.
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
### Independent Amaster employees
|
|
438
|
+
|
|
439
|
+
An additional Amaster employee has a distinct intent-derived binding, managed
|
|
440
|
+
workspace, journal and bridge. Automatic IAM registration excludes those bindings
|
|
441
|
+
and always retains the original personal twin as the computer carrier. Only that
|
|
442
|
+
carrier polls enrollment intents or restores other desktop sessions; its bridge
|
|
443
|
+
runs with `computerCarrierOnly`, while managed Amaster bridges select one binding
|
|
444
|
+
and receive the bundled Pi runtime. This prevents duplicate workers after restart.
|
|
445
|
+
|
|
446
|
+
Managed Pi commands may read only the two desktop-generated runtime launcher
|
|
447
|
+
files in the profile cache, supplied locally by the desktop (never by remote
|
|
448
|
+
command payloads). The containing profile/cache directory remains unreadable;
|
|
449
|
+
these files are read-only under Seatbelt. This supports the owned Pi entrypoint
|
|
450
|
+
without exposing profile credentials or allowing a global runtime installation.
|
|
451
|
+
|
|
452
|
+
The existing company membership, employee-creation permission, hiring approval,
|
|
453
|
+
profile/generation and filesystem-confinement gates also apply to managed Amaster
|
|
454
|
+
employees. Sign-out/sign-in restores their existing references on the original
|
|
455
|
+
computer, without creating employees or replaying tasks. Moving these employees
|
|
456
|
+
to a replacement computer is not supported by the legacy directory-recovery
|
|
457
|
+
protocol; both server and UI reject that path rather than dropping confinement.
|
|
458
|
+
|
|
459
|
+
### Review hardening: command secrets, logout, and legacy journals
|
|
460
|
+
|
|
461
|
+
Managed Seatbelt reads are restricted to the current task, its exact private command configuration, approved launcher files and installed system/runtime roots. Other system temporary directories are not readable, including through `/var` aliases or task symlinks; required system alias metadata, shell selection and public CA files remain accessible. This policy is local to managed device commands, not the platform sandbox.
|
|
462
|
+
|
|
463
|
+
The task-local `node` launcher resolves to the host process's canonical executable. When Node is installed by a version manager or CI tool cache outside the system roots, only that executable is additionally readable; its parent directories allow metadata lookup but not listing, adjacent-file reads or writes.
|
|
464
|
+
|
|
465
|
+
Explicit enrollment participates in the desktop lifecycle transition and receives its cancellation signal. Logout waits for it before clearing device credentials. A claim response arriving after cancellation retains only a signed-out binding reference and revokes that response's narrow session; cancellation is checked on both sides of credential persistence.
|
|
466
|
+
|
|
467
|
+
Before adopting a legacy company or single-tool journal into the identity-scoped path, the bridge checks its saved configuration against the authenticated binding, company, server, user and generation. It persists the migrated journal before replay/reconciliation and leaves the original evidence file in place. A different binding cannot inherit results or configuration cleanup references.
|
|
468
|
+
|
|
469
|
+
### Local executable discovery
|
|
470
|
+
|
|
471
|
+
Codex is launched through the canonical path of the selected local executable,
|
|
472
|
+
so Homebrew symlinks do not hide companion binaries such as
|
|
473
|
+
`codex-code-mode-host`. This does not change the selected installation or copy
|
|
474
|
+
authentication from another environment. Managed macOS commands can inspect
|
|
475
|
+
ancestor metadata of existing system read roots (for example `/opt` for
|
|
476
|
+
`/opt/homebrew`) so Python can resolve its runtime; ancestor directory contents
|
|
477
|
+
and neighboring task files remain denied.
|
|
478
|
+
|
|
479
|
+
A finished journal receipt is retried independently of command polling: the server may have committed a terminal result before the response was lost. This retries output/receipt delivery only; it never relaunches the process. Unacknowledged records remain durable, with at least ten seconds between attempt starts per command, shared by poll replay and background recovery (including the first failed completion). The oldest eligible receipt is selected first so a rejected command cannot starve later receipts during long polling; replay always uses the journal’s original claim.
|
|
480
|
+
|
|
481
|
+
During a requested drain, an unacknowledged finished journal is reported as
|
|
482
|
+
requiring reconciliation after attempts already in flight settle. Neither
|
|
483
|
+
background recovery nor poll replay starts a new receipt attempt after drain,
|
|
484
|
+
even when multiple previous attempts overlap and continue failing. The bridge
|
|
485
|
+
retains the result and does not claim a clean drain or re-execute the command.
|
|
486
|
+
Normal startup retries durable receipts for Pi, Codex and WorkBuddy; only a
|
|
487
|
+
successful acknowledgement removes the journal entry. Authorization/server
|
|
488
|
+
failures keep it available for a later normal reconnect.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codebuddy-auth.d.ts","sourceRoot":"","sources":["../src/codebuddy-auth.ts"],"names":[],"mappings":"AAeA,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,SAAe,QAiDlF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Opaque desktop-auth copy. Never parses, decrypts, logs, or writes the source. */
|
|
2
|
+
import { constants, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const relativeAuth = path.join("Library", "Application Support", "CodeBuddyExtension", "Data", "Public", "auth", "workbuddy-desktop.info");
|
|
6
|
+
// Directory link counts legitimately change when another local test/project creates a child.
|
|
7
|
+
// File link counts remain part of auth identity; ancestors still require matching dev/inode/owner/mode.
|
|
8
|
+
const same = (a, b) => a.dev === b.dev && a.ino === b.ino && a.uid === b.uid && a.mode === b.mode && (!a.isFile() || a.nlink === b.nlink);
|
|
9
|
+
function present(file) {
|
|
10
|
+
try {
|
|
11
|
+
return lstatSync(file);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code === "ENOENT")
|
|
15
|
+
return null;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// sourceHome is supplied only by local host code (os.homedir), never a command payload.
|
|
20
|
+
export function snapshotWorkbuddyAuth(targetHome, sourceHome = os.homedir()) {
|
|
21
|
+
if (process.platform !== "darwin" || !process.getuid)
|
|
22
|
+
throw new Error("WorkBuddy desktop authentication is qualified only on macOS");
|
|
23
|
+
const source = path.join(sourceHome, relativeAuth);
|
|
24
|
+
const loggedOut = `${source}.logged-out`;
|
|
25
|
+
if (present(loggedOut))
|
|
26
|
+
throw new Error("Sign in to WorkBuddy before starting a task");
|
|
27
|
+
const chain = [];
|
|
28
|
+
let current = path.parse(source).root;
|
|
29
|
+
for (const component of ["", ...path.relative(current, source).split(path.sep)]) {
|
|
30
|
+
current = component ? path.join(current, component) : current;
|
|
31
|
+
const entry = present(current);
|
|
32
|
+
const isFile = current === source;
|
|
33
|
+
if (!entry)
|
|
34
|
+
throw new Error("WorkBuddy desktop login is missing");
|
|
35
|
+
if (entry.isSymbolicLink() || (isFile ? !entry.isFile() : !entry.isDirectory())
|
|
36
|
+
|| (entry.uid !== process.getuid() && (isFile || entry.uid !== 0))
|
|
37
|
+
|| ((entry.mode & 0o022) && !(entry.uid === 0 && !isFile && (entry.mode & 0o1000)))) {
|
|
38
|
+
throw new Error("WorkBuddy authentication path has unsafe ownership, permissions, or links");
|
|
39
|
+
}
|
|
40
|
+
if (isFile && (entry.nlink !== 1 || entry.size <= 0 || entry.size > 4 * 1024 * 1024))
|
|
41
|
+
throw new Error("Invalid WorkBuddy authentication file size");
|
|
42
|
+
chain.push({ file: current, stat: entry });
|
|
43
|
+
}
|
|
44
|
+
const descriptors = [];
|
|
45
|
+
let bytes;
|
|
46
|
+
try {
|
|
47
|
+
for (const { file, stat } of chain) {
|
|
48
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | (file === source ? 0 : constants.O_DIRECTORY));
|
|
49
|
+
descriptors.push(fd);
|
|
50
|
+
const before = fstatSync(fd);
|
|
51
|
+
if (!same(before, stat))
|
|
52
|
+
throw new Error("WorkBuddy authentication changed during validation");
|
|
53
|
+
if (file === source) {
|
|
54
|
+
bytes = readFileSync(fd);
|
|
55
|
+
const after = fstatSync(fd);
|
|
56
|
+
if (!same(before, after) || before.size !== after.size || before.mtimeMs !== after.mtimeMs || bytes.length !== before.size) {
|
|
57
|
+
throw new Error("WorkBuddy authentication changed during snapshot");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const { file, stat } of chain) {
|
|
62
|
+
const after = lstatSync(file);
|
|
63
|
+
if (after.isSymbolicLink() || !same(stat, after))
|
|
64
|
+
throw new Error("WorkBuddy authentication path changed during snapshot");
|
|
65
|
+
}
|
|
66
|
+
if (present(loggedOut))
|
|
67
|
+
throw new Error("WorkBuddy logged out during snapshot");
|
|
68
|
+
if (!bytes)
|
|
69
|
+
throw new Error("WorkBuddy authentication snapshot is empty");
|
|
70
|
+
const target = path.join(targetHome, relativeAuth);
|
|
71
|
+
mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
72
|
+
writeFileSync(target, bytes, { flag: "wx", mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
bytes?.fill(0);
|
|
76
|
+
for (const fd of descriptors.reverse())
|
|
77
|
+
closeSync(fd);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=codebuddy-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codebuddy-auth.js","sourceRoot":"","sources":["../src/codebuddy-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConfigurationReference } from "./configuration-lifetime.js";
|
|
2
|
+
/** Locks fence both native-session mutation and workspace writes, including crash recovery. */
|
|
3
|
+
export declare function acquireCodebuddyLocks(stateRoot: string, keys: string[], recovery?: {
|
|
4
|
+
directory: string;
|
|
5
|
+
persist: () => Promise<void>;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
release: () => Promise<void>;
|
|
8
|
+
save: (directory: string) => Promise<void>;
|
|
9
|
+
}>;
|
|
10
|
+
/** Invoked only after configuration ownership and stopped evidence have been validated. */
|
|
11
|
+
export declare function reclaimCodebuddyLocks(reference: ConfigurationReference, stateRoot: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=codebuddy-locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codebuddy-locks.d.ts","sourceRoot":"","sources":["../src/codebuddy-locks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,+FAA+F;AAC/F,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE;IACxF,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;;sBAqCuB,MAAM;GAI7B;AAED,2FAA2F;AAC3F,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,iBAkB/F"}
|