@corenel/sidecar 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +231 -181
  2. package/dist/cli.js +234 -70
  3. package/package.json +13 -14
package/CHANGELOG.md CHANGED
@@ -1,181 +1,231 @@
1
- # Changelog
2
-
3
- ## 0.3.1
4
-
5
- This package is Corenel's, and now says so in its defaults. 0.3.0 pointed its
6
- gateway, and the app it opens, at the sibling product.
7
-
8
- The direction is the fix: prompd.app is a CONSUMER of these APIs, so it names
9
- its own origin (`--app-url https://prompd.app/studio`, `--endpoint <its own>`)
10
- and corenel.ai is what you get without asking.
11
-
12
- ### Added
13
- - `sessions` subcommand: `corenel-sidecar sessions [list | show <id>]`, also
14
- reachable as `corenel sessions`. Read-only, and read from the SANDBOXES rather
15
- than from a daemon -- a subcommand runs in a new process and cannot see a
16
- running daemon's SessionManager, so anything derived from it would be a guess.
17
- Asking msb gets the same answer the daemon gets when it adopts at boot, and it
18
- works with nothing running.
19
-
20
- ### Changed
21
- - `--app-url` defaults to `https://corenel.ai`, and the open URL no longer has
22
- `/studio` appended to it. That is a prompd route; corenel serves its app at
23
- `/`, so the old shape would have opened a page that does not exist and lost
24
- the pairing fragment on the redirect. `--app-url` is now the app URL in full,
25
- path included -- prompd pairs at `https://prompd.app/studio`.
26
-
27
- ### Fixed
28
- - The daemon called `https://api.prompd.app` for every model run -- the sibling
29
- product's gateway, with a token minted against this one. The default endpoint
30
- is now `https://api.corenel.ai/api`.
31
- - That default was also missing the `/api` prefix the gateway is mounted behind.
32
- `setGatewayBase` appends `/v1`, so a bare origin composed
33
- `https://host/v1/chat/completions` and 404'd -- surfacing as "the run failed",
34
- nowhere near the flag responsible. A bare origin now has the prefix added, and
35
- any endpoint carrying a path is left exactly as given.
36
- - `--allow-lan` did not do what it says. It widened the CORS origin allowlist
37
- while the socket stayed bound to `127.0.0.1`, so nothing on the network could
38
- connect and the banner still printed a loopback URL. It now binds all
39
- interfaces and serves `wss://` with the self-signed cert (which already
40
- covered the LAN IPs for exactly this). Both halves defer to anything
41
- explicit: `--host` keeps its address, a supplied `--tls-cert` is not replaced.
42
-
43
- This is the only way LAN pairing can work from the product: the app is served
44
- over https, and a browser on an https page refuses a `ws://` connection to a
45
- LAN address.
46
-
47
- ## 0.3.0
48
-
49
- The isolation release. 0.2.0 could expose a machine's files and shell to a
50
- session and run crew agents unattended; it always did that work in its own
51
- process, on the host, with whatever reach the host had. This release puts a
52
- boundary around a run and lets an external coding agent be governed by it.
53
-
54
- Everything is additive and off by default. A 0.2.0 setup behaves exactly as it
55
- did unless you pass one of the new flags.
56
-
57
- ### Sessions in a sandbox
58
- - `--isolation <none|microvm>` chooses how a session confines a run. `none` is
59
- the old behaviour and stays the default; `microvm` boots a sandbox per session.
60
- - `--msb <path>` locates the microsandbox binary, and `--guest-image <ref>` picks
61
- what a session boots. Pin the image by digest for anything you snapshot -- a
62
- floating tag means a restored snapshot is not the thing that was captured.
63
-
64
- ### Proxying an external agent
65
- - `--acp <target>` runs an external coding agent (`claude-code`) under our
66
- policy rather than beside it. Needs `--root`.
67
- - `--acp-policy <standard|read-only|autonomous>` governs what the proxied agent
68
- may do, and `--acp-auth <subscription|api-key|inherit>` selects the credential
69
- it uses.
70
- - `--agent <name>` names the crew agent an `acp-agent` session drives.
71
-
72
- ### Boundaries the operator sets
73
- - `--clone-under <dir>` lets a session clone a repository, and nothing more. The
74
- clone runs on the host with your own git credentials; they never enter the
75
- session.
76
- - `--keep-transcripts` PERMITS a run to keep its words rather than only its
77
- metadata. It is a ceiling, not a switch: each run still has to ask, and no run
78
- can ask past an operator who did not pass this.
79
- - `--allow-add-under <dir>` lets a client add working directories beneath a
80
- subtree during a session. This grants directory-name enumeration over that
81
- subtree, and added roots last only until the sidecar exits.
82
-
83
- ### Fixed
84
- - `hosts list` -- the spelling `--help` documents -- was rejected as an unknown
85
- argument; only a bare `hosts` worked. Both now do.
86
-
87
- ### Naming
88
- - The `corenel` binary this package briefly declared is gone; it collided with
89
- `@corenel/cli`, which owns that name. This package installs `corenel-sidecar`,
90
- and `corenel sidecar …` reaches the same program through the CLI. `--help`
91
- now reports whichever name you invoked.
92
-
93
- ## 0.2.0
94
-
95
- Ships `--help`, and moves onto the workspace's shared version line — every
96
- `@corenel/*` package is now 0.2.0, so a compatible set is obvious at a glance.
97
-
98
- - `--help` / `-h` print usage and exit 0. They were previously rejected as
99
- unknown arguments, which printed a bare flag list and exited 1 — the first
100
- thing anyone types against an unfamiliar binary. The text is generated from the
101
- same table the parser validates against, so a flag cannot be added without
102
- appearing in `--help`. This landed just after 0.1.6 was published, so it has
103
- not been in a release until now.
104
-
105
- No behaviour changes to the daemon, tunnels, or host sync. Upgrading from 0.1.6
106
- is safe; everything in the 0.1.6 notes below still applies.
107
-
108
- ## 0.1.6
109
-
110
- The daemon release. `@corenel/sidecar` could already expose a machine's files and
111
- shell to a browser session; it can now also **run crew agents on its own** —
112
- on a schedule, when files change, or when another run finishes — with budgets,
113
- permissions and per-owner isolation enforced node-side.
114
-
115
- Everything below is additive. An existing `pd sidecar` setup behaves as it did in
116
- 0.1.5 unless you pass `--daemon`.
117
-
118
- ### Daemon core
119
- - `--daemon` starts an always-on core: an interruptible clock (sleep-until-next-wake,
120
- wake signal, max-sleep cap) driving a trigger dispatcher over a shared runner.
121
- - Node run executor built on `runAgent`, with session recording to disk.
122
- - Per-agent run queue — one live run per agent, so a slow run cannot stack.
123
- - Node gateway client (absolute endpoint, node auth); daemon-origin requests are
124
- tagged so entitlement metering can tell them from browser traffic.
125
- - `--default-model` fallback; a crew agent with no model fails fast rather than
126
- starting an unrunnable run.
127
-
128
- ### Triggers
129
- - Minimal 5-field UTC cron matcher with per-agent trigger state (`lastFired`,
130
- `nextWake`), persisted and serialized so concurrent writes cannot clobber it.
131
- - File-change triggers: a self-contained glob matcher, a bounded change buffer,
132
- and an idempotent re-scan.
133
- - Run-complete triggers: an evaluator carrying owner, stop reason and run
134
- generation, with a chain-depth cap (`--max-chain-depth`, validated so a bad
135
- value cannot disable the cap) to stop runs triggering each other forever.
136
- - `schedule_self` tool for self-directed wakes.
137
-
138
- ### Budgets
139
- - Run budgets enforced node-side (stop / degrade), failing **closed** on an
140
- unpriceable run rather than proceeding unmetered.
141
- - A continuous trigger cannot become due without a budget cap.
142
- - Pure usage pricing extracted to the harness; the node side hydrates a price table.
143
-
144
- ### Permissions and attendance
145
- - Unattended prompt resolver: deny, park (bounded by the real budget remainder),
146
- or allow — an abort resolves to null rather than a silent deny.
147
- - Attendance registry: asks are broadcast to attached clients, first answer wins,
148
- and replay on attach means reconnecting does not lose a pending ask.
149
- - Pending-ask records with boot reconciliation (audit-only) and permission audit lines.
150
- - Stall watchdog aborts hung runs while leaving parked ones alone.
151
-
152
- ### Multi-owner isolation
153
- - Per-owner run state and owner-qualified dispatch keys; a multi-root schedule
154
- enumerates the daemon's own crew plus every `hosts/<dir>/crew`.
155
- - `call_agent` is confined to the caller's owner, and an attended run resolves its
156
- owner from the authenticated identity rather than a client-supplied value.
157
-
158
- ### Hosts and sync
159
- - Durable `sidecarId` with friendly-name resolution; the CLI advertises id and name.
160
- - `hosts.json` manifest: sanitized frozen `dirName`, null-prototype parsing,
161
- per-record validation, atomic writes and serialized read-modify-write.
162
- - `pd sidecar hosts list` / `forget`; boot logs manifest drift.
163
- - Host-sync service with daemon-derived destinations. Writes to a synced tree go
164
- through `syncWrite`/`syncRemove` only — the exposed surface has no `write`/`remove`
165
- to bypass, and path confinement is reused from `NodeFileService` rather than
166
- reimplemented.
167
- - A materialized directory is never reclaimed by eviction.
168
-
169
- ### Also
170
- - `--help` / `-h` now print usage and exit 0. They were previously rejected as
171
- unknown arguments, which printed a bare flag list and exited 1. The help text is
172
- generated from the same table the parser validates against, so a flag cannot be
173
- added without appearing in `--help`.
174
- - `fs_delete` tool, wired to `SidecarFileService.remove`.
175
- - Quieter cloudflared output.
176
- - A second `hello` is ignored once authenticated.
177
-
178
- ### Housekeeping
179
- - The package's own `typecheck` now passes. It shares modules with the browser
180
- harness (which guards its browser bits at runtime), so the tsconfig lacked the
181
- DOM *types* and reported a dozen phantom errors against correct code.
1
+ # Changelog
2
+
3
+ ## 0.3.2
4
+
5
+ Sessions stop being something you can only list. This release gives the sandbox
6
+ a full lifecycle from the command line, and the verbs are the port's own --
7
+ create/destroy, not add/remove: you add an entry to a list, you create and
8
+ destroy a thing that boots and holds memory.
9
+
10
+ Everything goes through the SAME backend the daemon uses. Two ways to make a
11
+ sandbox would stamp labels two ways and drift on limits.
12
+
13
+ ### Added
14
+ - Session lifecycle: `corenel sessions create [--root <dir>]`, `run <id>
15
+ "<prompt>"`, and `pause|resume|destroy <id>`. `corenel run --session <id>`
16
+ forwards to the same place. `stop` is deliberately absent -- the port has
17
+ pause (stop admitting work, bounded drain, and it reports whether it drained)
18
+ and destroy, and a third word over two real concepts is a translation nobody
19
+ asked for.
20
+ - `exec` takes an optional env, threaded to `msb exec -e`. Without it the CLI
21
+ inside the guest answers "not signed in" -- the image ships no token and
22
+ create injects none -- so the sandbox was a box nothing useful could happen
23
+ in. It carries the HOST's identity: a session isolates the filesystem and the
24
+ process, not the wallet.
25
+
26
+ ### Fixed
27
+ Running against a real hypervisor found five defects that reading the code did
28
+ not:
29
+
30
+ - `create` always allocated `sess_1`. The id counter lives in the backend and
31
+ starts at zero, so the second create on a machine collided with the first.
32
+ Adoption is what advances it past what already exists -- the daemon does this
33
+ at boot and a one-shot command needs it for the same reason. The same fix is
34
+ what made `run` work at all.
35
+ - `sessions create --root X` silently used the CWD. The dispatcher stripped
36
+ flags before the subcommand could read them, so the value was dropped without
37
+ a word. The whole argv is passed through now, and the verbs are the non-flag
38
+ words.
39
+ - A created session reported `created 1787430794235` -- the label stores epoch
40
+ millis and nothing rendered it.
41
+ - A STOPPED session vanished from the list: `msb inspect` returns no labels for
42
+ one, so there was nothing to read. It is recovered from the sandbox name,
43
+ which the backend reserves for exactly this.
44
+ - ...and was therefore unrecoverable, since resume and destroy both look it up
45
+ first. `adopt()` also refuses a session whose workdir label it cannot read, so
46
+ the backend declined too and destroy reported success having done nothing.
47
+ There is now a direct-to-sandbox path used only after the backend declines,
48
+ and destroy VERIFIES the session is gone instead of assuming.
49
+
50
+ Verified end to end against real microVMs: create -> list -> show -> pause ->
51
+ resume -> destroy, and a run reaching the guest's own CLI.
52
+
53
+ ## 0.3.1
54
+
55
+ This package is Corenel's, and now says so in its defaults. 0.3.0 pointed its
56
+ gateway, and the app it opens, at the sibling product.
57
+
58
+ The direction is the fix: prompd.app is a CONSUMER of these APIs, so it names
59
+ its own origin (`--app-url https://prompd.app/studio`, `--endpoint <its own>`)
60
+ and corenel.ai is what you get without asking.
61
+
62
+ ### Added
63
+ - `sessions` subcommand: `corenel-sidecar sessions [list | show <id>]`, also
64
+ reachable as `corenel sessions`. Read-only, and read from the SANDBOXES rather
65
+ than from a daemon -- a subcommand runs in a new process and cannot see a
66
+ running daemon's SessionManager, so anything derived from it would be a guess.
67
+ Asking msb gets the same answer the daemon gets when it adopts at boot, and it
68
+ works with nothing running.
69
+
70
+ ### Changed
71
+ - `--app-url` defaults to `https://corenel.ai`, and the open URL no longer has
72
+ `/studio` appended to it. That is a prompd route; corenel serves its app at
73
+ `/`, so the old shape would have opened a page that does not exist and lost
74
+ the pairing fragment on the redirect. `--app-url` is now the app URL in full,
75
+ path included -- prompd pairs at `https://prompd.app/studio`.
76
+
77
+ ### Fixed
78
+ - The daemon called `https://api.prompd.app` for every model run -- the sibling
79
+ product's gateway, with a token minted against this one. The default endpoint
80
+ is now `https://api.corenel.ai/api`.
81
+ - That default was also missing the `/api` prefix the gateway is mounted behind.
82
+ `setGatewayBase` appends `/v1`, so a bare origin composed
83
+ `https://host/v1/chat/completions` and 404'd -- surfacing as "the run failed",
84
+ nowhere near the flag responsible. A bare origin now has the prefix added, and
85
+ any endpoint carrying a path is left exactly as given.
86
+ - `--allow-lan` did not do what it says. It widened the CORS origin allowlist
87
+ while the socket stayed bound to `127.0.0.1`, so nothing on the network could
88
+ connect and the banner still printed a loopback URL. It now binds all
89
+ interfaces and serves `wss://` with the self-signed cert (which already
90
+ covered the LAN IPs for exactly this). Both halves defer to anything
91
+ explicit: `--host` keeps its address, a supplied `--tls-cert` is not replaced.
92
+
93
+ This is the only way LAN pairing can work from the product: the app is served
94
+ over https, and a browser on an https page refuses a `ws://` connection to a
95
+ LAN address.
96
+
97
+ ## 0.3.0
98
+
99
+ The isolation release. 0.2.0 could expose a machine's files and shell to a
100
+ session and run crew agents unattended; it always did that work in its own
101
+ process, on the host, with whatever reach the host had. This release puts a
102
+ boundary around a run and lets an external coding agent be governed by it.
103
+
104
+ Everything is additive and off by default. A 0.2.0 setup behaves exactly as it
105
+ did unless you pass one of the new flags.
106
+
107
+ ### Sessions in a sandbox
108
+ - `--isolation <none|microvm>` chooses how a session confines a run. `none` is
109
+ the old behaviour and stays the default; `microvm` boots a sandbox per session.
110
+ - `--msb <path>` locates the microsandbox binary, and `--guest-image <ref>` picks
111
+ what a session boots. Pin the image by digest for anything you snapshot -- a
112
+ floating tag means a restored snapshot is not the thing that was captured.
113
+
114
+ ### Proxying an external agent
115
+ - `--acp <target>` runs an external coding agent (`claude-code`) under our
116
+ policy rather than beside it. Needs `--root`.
117
+ - `--acp-policy <standard|read-only|autonomous>` governs what the proxied agent
118
+ may do, and `--acp-auth <subscription|api-key|inherit>` selects the credential
119
+ it uses.
120
+ - `--agent <name>` names the crew agent an `acp-agent` session drives.
121
+
122
+ ### Boundaries the operator sets
123
+ - `--clone-under <dir>` lets a session clone a repository, and nothing more. The
124
+ clone runs on the host with your own git credentials; they never enter the
125
+ session.
126
+ - `--keep-transcripts` PERMITS a run to keep its words rather than only its
127
+ metadata. It is a ceiling, not a switch: each run still has to ask, and no run
128
+ can ask past an operator who did not pass this.
129
+ - `--allow-add-under <dir>` lets a client add working directories beneath a
130
+ subtree during a session. This grants directory-name enumeration over that
131
+ subtree, and added roots last only until the sidecar exits.
132
+
133
+ ### Fixed
134
+ - `hosts list` -- the spelling `--help` documents -- was rejected as an unknown
135
+ argument; only a bare `hosts` worked. Both now do.
136
+
137
+ ### Naming
138
+ - The `corenel` binary this package briefly declared is gone; it collided with
139
+ `@corenel/cli`, which owns that name. This package installs `corenel-sidecar`,
140
+ and `corenel sidecar …` reaches the same program through the CLI. `--help`
141
+ now reports whichever name you invoked.
142
+
143
+ ## 0.2.0
144
+
145
+ Ships `--help`, and moves onto the workspace's shared version line — every
146
+ `@corenel/*` package is now 0.2.0, so a compatible set is obvious at a glance.
147
+
148
+ - `--help` / `-h` print usage and exit 0. They were previously rejected as
149
+ unknown arguments, which printed a bare flag list and exited 1 — the first
150
+ thing anyone types against an unfamiliar binary. The text is generated from the
151
+ same table the parser validates against, so a flag cannot be added without
152
+ appearing in `--help`. This landed just after 0.1.6 was published, so it has
153
+ not been in a release until now.
154
+
155
+ No behaviour changes to the daemon, tunnels, or host sync. Upgrading from 0.1.6
156
+ is safe; everything in the 0.1.6 notes below still applies.
157
+
158
+ ## 0.1.6
159
+
160
+ The daemon release. `@corenel/sidecar` could already expose a machine's files and
161
+ shell to a browser session; it can now also **run crew agents on its own** —
162
+ on a schedule, when files change, or when another run finishes — with budgets,
163
+ permissions and per-owner isolation enforced node-side.
164
+
165
+ Everything below is additive. An existing `pd sidecar` setup behaves as it did in
166
+ 0.1.5 unless you pass `--daemon`.
167
+
168
+ ### Daemon core
169
+ - `--daemon` starts an always-on core: an interruptible clock (sleep-until-next-wake,
170
+ wake signal, max-sleep cap) driving a trigger dispatcher over a shared runner.
171
+ - Node run executor built on `runAgent`, with session recording to disk.
172
+ - Per-agent run queue — one live run per agent, so a slow run cannot stack.
173
+ - Node gateway client (absolute endpoint, node auth); daemon-origin requests are
174
+ tagged so entitlement metering can tell them from browser traffic.
175
+ - `--default-model` fallback; a crew agent with no model fails fast rather than
176
+ starting an unrunnable run.
177
+
178
+ ### Triggers
179
+ - Minimal 5-field UTC cron matcher with per-agent trigger state (`lastFired`,
180
+ `nextWake`), persisted and serialized so concurrent writes cannot clobber it.
181
+ - File-change triggers: a self-contained glob matcher, a bounded change buffer,
182
+ and an idempotent re-scan.
183
+ - Run-complete triggers: an evaluator carrying owner, stop reason and run
184
+ generation, with a chain-depth cap (`--max-chain-depth`, validated so a bad
185
+ value cannot disable the cap) to stop runs triggering each other forever.
186
+ - `schedule_self` tool for self-directed wakes.
187
+
188
+ ### Budgets
189
+ - Run budgets enforced node-side (stop / degrade), failing **closed** on an
190
+ unpriceable run rather than proceeding unmetered.
191
+ - A continuous trigger cannot become due without a budget cap.
192
+ - Pure usage pricing extracted to the harness; the node side hydrates a price table.
193
+
194
+ ### Permissions and attendance
195
+ - Unattended prompt resolver: deny, park (bounded by the real budget remainder),
196
+ or allow — an abort resolves to null rather than a silent deny.
197
+ - Attendance registry: asks are broadcast to attached clients, first answer wins,
198
+ and replay on attach means reconnecting does not lose a pending ask.
199
+ - Pending-ask records with boot reconciliation (audit-only) and permission audit lines.
200
+ - Stall watchdog aborts hung runs while leaving parked ones alone.
201
+
202
+ ### Multi-owner isolation
203
+ - Per-owner run state and owner-qualified dispatch keys; a multi-root schedule
204
+ enumerates the daemon's own crew plus every `hosts/<dir>/crew`.
205
+ - `call_agent` is confined to the caller's owner, and an attended run resolves its
206
+ owner from the authenticated identity rather than a client-supplied value.
207
+
208
+ ### Hosts and sync
209
+ - Durable `sidecarId` with friendly-name resolution; the CLI advertises id and name.
210
+ - `hosts.json` manifest: sanitized frozen `dirName`, null-prototype parsing,
211
+ per-record validation, atomic writes and serialized read-modify-write.
212
+ - `pd sidecar hosts list` / `forget`; boot logs manifest drift.
213
+ - Host-sync service with daemon-derived destinations. Writes to a synced tree go
214
+ through `syncWrite`/`syncRemove` only — the exposed surface has no `write`/`remove`
215
+ to bypass, and path confinement is reused from `NodeFileService` rather than
216
+ reimplemented.
217
+ - A materialized directory is never reclaimed by eviction.
218
+
219
+ ### Also
220
+ - `--help` / `-h` now print usage and exit 0. They were previously rejected as
221
+ unknown arguments, which printed a bare flag list and exited 1. The help text is
222
+ generated from the same table the parser validates against, so a flag cannot be
223
+ added without appearing in `--help`.
224
+ - `fs_delete` tool, wired to `SidecarFileService.remove`.
225
+ - Quieter cloudflared output.
226
+ - A second `hello` is ignored once authenticated.
227
+
228
+ ### Housekeeping
229
+ - The package's own `typecheck` now passes. It shares modules with the browser
230
+ harness (which guards its browser bits at runtime), so the tsconfig lacked the
231
+ DOM *types* and reported a dozen phantom errors against correct code.
package/dist/cli.js CHANGED
@@ -11908,7 +11908,7 @@ var require_runtime = __commonJS({
11908
11908
  }
11909
11909
  return p2 && p2.lookup(name);
11910
11910
  };
11911
- _proto.resolve = function resolve9(name, forWrite) {
11911
+ _proto.resolve = function resolve10(name, forWrite) {
11912
11912
  var p2 = forWrite && this.isolateWrites ? void 0 : this.parent;
11913
11913
  var val = this.variables[name];
11914
11914
  if (val !== void 0) {
@@ -13638,7 +13638,7 @@ var require_loader = __commonJS({
13638
13638
  return _EmitterObj.apply(this, arguments) || this;
13639
13639
  }
13640
13640
  var _proto = Loader2.prototype;
13641
- _proto.resolve = function resolve9(from, to) {
13641
+ _proto.resolve = function resolve10(from, to) {
13642
13642
  return path.resolve(path.dirname(from), to);
13643
13643
  };
13644
13644
  _proto.isRelative = function isRelative(filename) {
@@ -17098,7 +17098,7 @@ var require_lib3 = __commonJS({
17098
17098
  let accum = [];
17099
17099
  let accumBytes = 0;
17100
17100
  let abort = false;
17101
- return new Body.Promise(function(resolve9, reject) {
17101
+ return new Body.Promise(function(resolve10, reject) {
17102
17102
  let resTimeout;
17103
17103
  if (_this4.timeout) {
17104
17104
  resTimeout = setTimeout(function() {
@@ -17132,7 +17132,7 @@ var require_lib3 = __commonJS({
17132
17132
  }
17133
17133
  clearTimeout(resTimeout);
17134
17134
  try {
17135
- resolve9(Buffer.concat(accum, accumBytes));
17135
+ resolve10(Buffer.concat(accum, accumBytes));
17136
17136
  } catch (err) {
17137
17137
  reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
17138
17138
  }
@@ -17807,7 +17807,7 @@ var require_lib3 = __commonJS({
17807
17807
  throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
17808
17808
  }
17809
17809
  Body.Promise = fetch3.Promise;
17810
- return new fetch3.Promise(function(resolve9, reject) {
17810
+ return new fetch3.Promise(function(resolve10, reject) {
17811
17811
  const request = new Request3(url, opts);
17812
17812
  const options = getNodeRequestOptions(request);
17813
17813
  const send = (options.protocol === "https:" ? https : http).request;
@@ -17940,7 +17940,7 @@ var require_lib3 = __commonJS({
17940
17940
  requestOpts.body = void 0;
17941
17941
  requestOpts.headers.delete("content-length");
17942
17942
  }
17943
- resolve9(fetch3(new Request3(locationURL, requestOpts)));
17943
+ resolve10(fetch3(new Request3(locationURL, requestOpts)));
17944
17944
  finalize();
17945
17945
  return;
17946
17946
  }
@@ -17961,7 +17961,7 @@ var require_lib3 = __commonJS({
17961
17961
  const codings = headers.get("Content-Encoding");
17962
17962
  if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
17963
17963
  response = new Response3(body, response_options);
17964
- resolve9(response);
17964
+ resolve10(response);
17965
17965
  return;
17966
17966
  }
17967
17967
  const zlibOptions = {
@@ -17971,7 +17971,7 @@ var require_lib3 = __commonJS({
17971
17971
  if (codings == "gzip" || codings == "x-gzip") {
17972
17972
  body = body.pipe(zlib.createGunzip(zlibOptions));
17973
17973
  response = new Response3(body, response_options);
17974
- resolve9(response);
17974
+ resolve10(response);
17975
17975
  return;
17976
17976
  }
17977
17977
  if (codings == "deflate" || codings == "x-deflate") {
@@ -17983,12 +17983,12 @@ var require_lib3 = __commonJS({
17983
17983
  body = body.pipe(zlib.createInflateRaw());
17984
17984
  }
17985
17985
  response = new Response3(body, response_options);
17986
- resolve9(response);
17986
+ resolve10(response);
17987
17987
  });
17988
17988
  raw.on("end", function() {
17989
17989
  if (!response) {
17990
17990
  response = new Response3(body, response_options);
17991
- resolve9(response);
17991
+ resolve10(response);
17992
17992
  }
17993
17993
  });
17994
17994
  return;
@@ -17996,11 +17996,11 @@ var require_lib3 = __commonJS({
17996
17996
  if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
17997
17997
  body = body.pipe(zlib.createBrotliDecompress());
17998
17998
  response = new Response3(body, response_options);
17999
- resolve9(response);
17999
+ resolve10(response);
18000
18000
  return;
18001
18001
  }
18002
18002
  response = new Response3(body, response_options);
18003
- resolve9(response);
18003
+ resolve10(response);
18004
18004
  });
18005
18005
  writeToStream(req, request);
18006
18006
  });
@@ -22281,8 +22281,8 @@ var SidecarServer = class {
22281
22281
  }
22282
22282
  hostCall(provider, method, args) {
22283
22283
  const id = ++this.hostSeq;
22284
- return new Promise((resolve9, reject) => {
22285
- this.hostPending.set(id, { resolve: resolve9, reject });
22284
+ return new Promise((resolve10, reject) => {
22285
+ this.hostPending.set(id, { resolve: resolve10, reject });
22286
22286
  this.ch.send({ t: "host-call", id, provider, method, args });
22287
22287
  });
22288
22288
  }
@@ -22463,13 +22463,13 @@ function probeAddress(bindHost) {
22463
22463
  return h2;
22464
22464
  }
22465
22465
  function portInUse(host, port, timeoutMs = 500) {
22466
- return new Promise((resolve9) => {
22466
+ return new Promise((resolve10) => {
22467
22467
  let settled = false;
22468
22468
  const done = (busy) => {
22469
22469
  if (settled) return;
22470
22470
  settled = true;
22471
22471
  socket.destroy();
22472
- resolve9(busy);
22472
+ resolve10(busy);
22473
22473
  };
22474
22474
  const socket = connect({ host, port });
22475
22475
  socket.setTimeout(timeoutMs);
@@ -24361,8 +24361,9 @@ function inspectArgs(name) {
24361
24361
  function snapshotListArgs() {
24362
24362
  return ["snapshot", "list", "--format", "json"];
24363
24363
  }
24364
- function execArgs(name, command) {
24365
- return ["exec", name, "--", ...command];
24364
+ function execArgs(name, command, env) {
24365
+ const e2 = env ? Object.entries(env).flatMap(([k2, v2]) => ["-e", `${k2}=${v2}`]) : [];
24366
+ return ["exec", name, ...e2, "--", ...command];
24366
24367
  }
24367
24368
  function wslHostPath(p2) {
24368
24369
  const m2 = /^([A-Za-z]):[\\/](.*)$/.exec(p2);
@@ -24730,7 +24731,7 @@ function makeMicrovmBackend(deps) {
24730
24731
  throw new SessionUnavailable(id, "paused");
24731
24732
  }
24732
24733
  if (command.length === 0) throw new Error("exec needs a command");
24733
- const r2 = await deps.run(execArgs(sandboxName(id), command), opts);
24734
+ const r2 = await deps.run(execArgs(sandboxName(id), command, opts?.env), opts);
24734
24735
  return { code: r2.code, stdout: r2.stdout, stderr: r2.stderr };
24735
24736
  },
24736
24737
  /**
@@ -27289,10 +27290,10 @@ ${content}` : parentData.content;
27289
27290
  });
27290
27291
  this.registerFiltersOnEnv(env);
27291
27292
  const renderTimeout = 5e3;
27292
- const renderPromise = new Promise((resolve9, reject) => {
27293
+ const renderPromise = new Promise((resolve10, reject) => {
27293
27294
  try {
27294
27295
  const result = env.renderString(content, context.parameters);
27295
- resolve9(result);
27296
+ resolve10(result);
27296
27297
  } catch (error) {
27297
27298
  reject(error);
27298
27299
  }
@@ -29328,7 +29329,7 @@ ${err}`;
29328
29329
  }
29329
29330
  const outcome = ctx.signal.aborted ? null : await Promise.race([
29330
29331
  ctx.reviewEdit(ctx.callId),
29331
- new Promise((resolve9) => ctx.signal.addEventListener("abort", () => resolve9(null), { once: true }))
29332
+ new Promise((resolve10) => ctx.signal.addEventListener("abort", () => resolve10(null), { once: true }))
29332
29333
  ]);
29333
29334
  if (!outcome) return "The run ended before the user reviewed this proposal. Do NOT assume the edit was applied.";
29334
29335
  if (outcome.decision === "applied") {
@@ -31455,8 +31456,8 @@ function _addRequestID(value, response) {
31455
31456
  }
31456
31457
  var APIPromise = class _APIPromise extends Promise {
31457
31458
  constructor(responsePromise, parseResponse2 = defaultParseResponse) {
31458
- super((resolve9) => {
31459
- resolve9(null);
31459
+ super((resolve10) => {
31460
+ resolve10(null);
31460
31461
  });
31461
31462
  this.responsePromise = responsePromise;
31462
31463
  this.parseResponse = parseResponse2;
@@ -32031,7 +32032,7 @@ var startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
32031
32032
  var isAbsoluteURL = (url) => {
32032
32033
  return startsWithSchemeRegexp.test(url);
32033
32034
  };
32034
- var sleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
32035
+ var sleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
32035
32036
  var validatePositiveInteger = (name, n2) => {
32036
32037
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
32037
32038
  throw new OpenAIError(`${name} must be an integer`);
@@ -32464,12 +32465,12 @@ var EventStream = class {
32464
32465
  _EventStream_errored.set(this, false);
32465
32466
  _EventStream_aborted.set(this, false);
32466
32467
  _EventStream_catchingPromiseCreated.set(this, false);
32467
- __classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((resolve9, reject) => {
32468
- __classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise, resolve9, "f");
32468
+ __classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((resolve10, reject) => {
32469
+ __classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise, resolve10, "f");
32469
32470
  __classPrivateFieldSet7(this, _EventStream_rejectConnectedPromise, reject, "f");
32470
32471
  }), "f");
32471
- __classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((resolve9, reject) => {
32472
- __classPrivateFieldSet7(this, _EventStream_resolveEndPromise, resolve9, "f");
32472
+ __classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((resolve10, reject) => {
32473
+ __classPrivateFieldSet7(this, _EventStream_resolveEndPromise, resolve10, "f");
32473
32474
  __classPrivateFieldSet7(this, _EventStream_rejectEndPromise, reject, "f");
32474
32475
  }), "f");
32475
32476
  __classPrivateFieldGet8(this, _EventStream_connectedPromise, "f").catch(() => {
@@ -32553,11 +32554,11 @@ var EventStream = class {
32553
32554
  * const message = await stream.emitted('message') // rejects if the stream errors
32554
32555
  */
32555
32556
  emitted(event) {
32556
- return new Promise((resolve9, reject) => {
32557
+ return new Promise((resolve10, reject) => {
32557
32558
  __classPrivateFieldSet7(this, _EventStream_catchingPromiseCreated, true, "f");
32558
32559
  if (event !== "error")
32559
32560
  this.once("error", reject);
32560
- this.once(event, resolve9);
32561
+ this.once(event, resolve10);
32561
32562
  });
32562
32563
  }
32563
32564
  async done() {
@@ -32710,7 +32711,7 @@ var AssistantStream = class _AssistantStream extends EventStream {
32710
32711
  if (done) {
32711
32712
  return { value: void 0, done: true };
32712
32713
  }
32713
- return new Promise((resolve9, reject) => readQueue.push({ resolve: resolve9, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
32714
+ return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
32714
32715
  }
32715
32716
  const chunk = pushQueue.shift();
32716
32717
  return { value: chunk, done: false };
@@ -34328,7 +34329,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
34328
34329
  if (done) {
34329
34330
  return { value: void 0, done: true };
34330
34331
  }
34331
- return new Promise((resolve9, reject) => readQueue.push({ resolve: resolve9, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
34332
+ return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
34332
34333
  }
34333
34334
  const chunk = pushQueue.shift();
34334
34335
  return { value: chunk, done: false };
@@ -35977,7 +35978,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
35977
35978
  if (done) {
35978
35979
  return { value: void 0, done: true };
35979
35980
  }
35980
- return new Promise((resolve9, reject) => readQueue.push({ resolve: resolve9, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
35981
+ return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
35981
35982
  }
35982
35983
  const event = pushQueue.shift();
35983
35984
  return { value: event, done: false };
@@ -36782,14 +36783,14 @@ function classifyStreamError(message, status) {
36782
36783
  }
36783
36784
  function abortableSleep(ms, signal) {
36784
36785
  if (signal?.aborted) return Promise.resolve(false);
36785
- return new Promise((resolve9) => {
36786
+ return new Promise((resolve10) => {
36786
36787
  const onAbort = () => {
36787
36788
  clearTimeout(t2);
36788
- resolve9(false);
36789
+ resolve10(false);
36789
36790
  };
36790
36791
  const t2 = setTimeout(() => {
36791
36792
  signal?.removeEventListener("abort", onAbort);
36792
- resolve9(true);
36793
+ resolve10(true);
36793
36794
  }, ms);
36794
36795
  signal?.addEventListener("abort", onAbort, { once: true });
36795
36796
  });
@@ -37734,12 +37735,12 @@ function makeScheduleSelfTool(opts) {
37734
37735
  var PARK_TIMEOUT_MS = 30 * 60 * 1e3;
37735
37736
  var PARK_POLL_MS = 250;
37736
37737
  function defaultSleep(ms, signal) {
37737
- return new Promise((resolve9) => {
37738
+ return new Promise((resolve10) => {
37738
37739
  let timer;
37739
37740
  const onAbort = () => {
37740
37741
  if (timer !== void 0) clearTimeout(timer);
37741
37742
  signal.removeEventListener("abort", onAbort);
37742
- resolve9();
37743
+ resolve10();
37743
37744
  };
37744
37745
  if (signal.aborted) {
37745
37746
  onAbort();
@@ -37747,7 +37748,7 @@ function defaultSleep(ms, signal) {
37747
37748
  }
37748
37749
  timer = setTimeout(() => {
37749
37750
  signal.removeEventListener("abort", onAbort);
37750
- resolve9();
37751
+ resolve10();
37751
37752
  }, ms);
37752
37753
  signal.addEventListener("abort", onAbort);
37753
37754
  });
@@ -38492,12 +38493,12 @@ function makeDispatcher(opts) {
38492
38493
  // src/runner/clock.ts
38493
38494
  var MAX_SLEEP_MS = 15 * 60 * 1e3;
38494
38495
  function defaultSleep2(ms, signal) {
38495
- return new Promise((resolve9) => {
38496
+ return new Promise((resolve10) => {
38496
38497
  let timer;
38497
38498
  const onAbort = () => {
38498
38499
  if (timer !== void 0) clearTimeout(timer);
38499
38500
  signal.removeEventListener("abort", onAbort);
38500
- resolve9();
38501
+ resolve10();
38501
38502
  };
38502
38503
  if (signal.aborted) {
38503
38504
  onAbort();
@@ -38505,7 +38506,7 @@ function defaultSleep2(ms, signal) {
38505
38506
  }
38506
38507
  timer = setTimeout(() => {
38507
38508
  signal.removeEventListener("abort", onAbort);
38508
- resolve9();
38509
+ resolve10();
38509
38510
  }, ms);
38510
38511
  signal.addEventListener("abort", onAbort);
38511
38512
  });
@@ -38869,7 +38870,7 @@ function makeAttendanceRegistry(opts) {
38869
38870
  if (clients.size === 0) {
38870
38871
  return Promise.resolve(null);
38871
38872
  }
38872
- return new Promise((resolve9) => {
38873
+ return new Promise((resolve10) => {
38873
38874
  let cleanupAbort;
38874
38875
  const settleNull = () => {
38875
38876
  const entry = outstanding.get(ask.id);
@@ -38881,7 +38882,7 @@ function makeAttendanceRegistry(opts) {
38881
38882
  ask,
38882
38883
  resolve: (decision) => {
38883
38884
  cleanupAbort?.();
38884
- resolve9(decision);
38885
+ resolve10(decision);
38885
38886
  }
38886
38887
  });
38887
38888
  for (const c2 of clients) {
@@ -39375,6 +39376,23 @@ async function runHostsCli(argv) {
39375
39376
  }
39376
39377
 
39377
39378
  // src/sessionsCli.ts
39379
+ import { resolve as resolve6 } from "node:path";
39380
+ async function sandboxLifecycle(run, verb, name) {
39381
+ const call = async (args) => {
39382
+ const r2 = await run(args).catch((e2) => ({ code: 1, stdout: "", stderr: String(e2) }));
39383
+ return { ok: r2.code === 0, detail: (r2.stderr || r2.stdout).trim().split("\n").slice(-2).join("; ") };
39384
+ };
39385
+ if (verb === "pause") return call(["stop", name]);
39386
+ if (verb === "resume") return call(["start", name]);
39387
+ await call(["stop", name]);
39388
+ return call(["remove", name]);
39389
+ }
39390
+ function humanTime(v2) {
39391
+ if (!v2) return "";
39392
+ const n2 = Number(v2);
39393
+ if (!Number.isFinite(n2) || n2 <= 0) return v2;
39394
+ return new Date(n2).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
39395
+ }
39378
39396
  async function listSessions(run) {
39379
39397
  const listed = await run(listArgs()).catch(() => null);
39380
39398
  if (!listed || listed.code !== 0) return [];
@@ -39382,7 +39400,7 @@ async function listSessions(run) {
39382
39400
  for (const box of parseSandboxList(listed.stdout)) {
39383
39401
  const inspected = await run(inspectArgs(box.name)).catch(() => null);
39384
39402
  const labels = inspected && inspected.code === 0 ? parseSandboxLabels(inspected.stdout) : {};
39385
- const id = labels[LABEL_SESSION];
39403
+ const id = labels[LABEL_SESSION] ?? idFromSandboxName(box.name);
39386
39404
  if (!id) continue;
39387
39405
  const url = labels[LABEL_REPO_URL];
39388
39406
  const ref = labels[LABEL_REPO_REF];
@@ -39392,7 +39410,9 @@ async function listSessions(run) {
39392
39410
  status: box.status || "unknown",
39393
39411
  workdir: labels[LABEL_WORKDIR] ?? "",
39394
39412
  owner: labels[LABEL_OWNER] ?? "",
39395
- created: labels[LABEL_CREATED] ?? (box.createdAt ? new Date(box.createdAt).toISOString() : ""),
39413
+ // The label stores epoch millis; a bare 1787430794235 in a terminal is a
39414
+ // number nobody can read as a time.
39415
+ created: humanTime(labels[LABEL_CREATED]) || (box.createdAt ? new Date(box.createdAt).toISOString() : ""),
39396
39416
  ...url ? { repo: ref ? `${url}#${ref}` : url } : {}
39397
39417
  });
39398
39418
  }
@@ -39428,11 +39448,28 @@ function formatSession(r2) {
39428
39448
  ...r2.repo ? [`repo ${r2.repo}`] : []
39429
39449
  ].join("\n");
39430
39450
  }
39451
+ var DEFAULT_GUEST_IMAGE = "corenel-guest:dev";
39452
+ function backendFor(deps, roots, run) {
39453
+ return makeMicrovmBackend({
39454
+ roots: () => roots,
39455
+ run,
39456
+ image: deps.image ?? DEFAULT_GUEST_IMAGE,
39457
+ // On Windows msb lives inside WSL, where C:\git\proj means nothing. The
39458
+ // daemon maps host paths the same way.
39459
+ ...deps.wsl ? { hostPath: wslHostPath } : {}
39460
+ });
39461
+ }
39462
+ var VALUE_FLAGS = /* @__PURE__ */ new Set(["--root", "--msb", "--image"]);
39463
+ function rootFrom(argv, cwd) {
39464
+ const at2 = argv.indexOf("--root");
39465
+ return resolve6(at2 >= 0 && argv[at2 + 1] ? argv[at2 + 1] : cwd);
39466
+ }
39431
39467
  async function runSessionsCli(argv, deps) {
39432
39468
  const out = deps.out ?? ((s2) => process.stdout.write(s2));
39433
39469
  const err = deps.err ?? ((s2) => process.stderr.write(s2));
39434
39470
  const run = deps.run ?? makeMsbRunner(deps.msbBin, deps.wsl ? wslMsb(deps.msbBin) : void 0);
39435
- const [verb, ...rest] = argv;
39471
+ const verbs = argv.filter((a2, i2) => !a2.startsWith("--") && !(i2 > 0 && VALUE_FLAGS.has(argv[i2 - 1])));
39472
+ const [verb, ...rest] = verbs;
39436
39473
  if (verb === void 0 || verb === "list") {
39437
39474
  if (rest.length) {
39438
39475
  err(`corenel sessions list: unexpected argument "${rest[0]}"
@@ -39459,15 +39496,143 @@ async function runSessionsCli(argv, deps) {
39459
39496
  `);
39460
39497
  return 0;
39461
39498
  }
39499
+ if (verb === "create") {
39500
+ const root = rootFrom(argv, process.cwd());
39501
+ const backend = deps.backend ?? backendFor(deps, [root], run);
39502
+ if (backend.adopt) await backend.adopt().catch(() => void 0);
39503
+ try {
39504
+ const handle = await backend.create({ workdir: root, owner: process.env.USERNAME || process.env.USER || "cli" });
39505
+ out(`${handle.id}
39506
+ `);
39507
+ err(`created ${handle.id} on ${handle.workdir}
39508
+ `);
39509
+ return 0;
39510
+ } catch (e2) {
39511
+ err(`corenel sessions create: ${e2 instanceof Error ? e2.message : String(e2)}
39512
+ `);
39513
+ return 1;
39514
+ }
39515
+ }
39516
+ if (verb === "run") {
39517
+ const id = rest[0];
39518
+ const prompt = rest.slice(1).filter((a2) => !a2.startsWith("--")).join(" ").trim();
39519
+ if (!id) {
39520
+ err("corenel sessions run: missing <id>\n");
39521
+ return 1;
39522
+ }
39523
+ if (!prompt) {
39524
+ err('corenel sessions run: missing prompt\n usage: corenel sessions run <id> "<prompt>"\n');
39525
+ return 1;
39526
+ }
39527
+ const found = (await listSessions(run)).find((r2) => r2.id === id || r2.sandbox === id);
39528
+ if (!found) {
39529
+ err(`corenel sessions: no session "${id}" on this machine
39530
+ `);
39531
+ return 1;
39532
+ }
39533
+ const backend = deps.backend ?? backendFor(deps, [found.workdir], run);
39534
+ if (!backend.exec) {
39535
+ err("corenel sessions run: this backend cannot execute inside a session\n");
39536
+ return 1;
39537
+ }
39538
+ if (backend.adopt) await backend.adopt().catch(() => void 0);
39539
+ try {
39540
+ const token = await readAuthToken() ?? process.env.CORENEL_TOKEN ?? null;
39541
+ if (!token) {
39542
+ err("corenel sessions run: not signed in on this host.\n Run `corenel login` first \u2014 the session uses the host's credential.\n");
39543
+ return 1;
39544
+ }
39545
+ const r2 = await backend.exec(found.id, ["corenel", "run", prompt], { env: { CORENEL_TOKEN: token } });
39546
+ if (r2.stdout) out(r2.stdout.endsWith("\n") ? r2.stdout : `${r2.stdout}
39547
+ `);
39548
+ if (r2.stderr.trim()) err(r2.stderr.endsWith("\n") ? r2.stderr : `${r2.stderr}
39549
+ `);
39550
+ return r2.code ?? 1;
39551
+ } catch (e2) {
39552
+ err(`corenel sessions run: ${e2 instanceof Error ? e2.message : String(e2)}
39553
+ `);
39554
+ return 1;
39555
+ }
39556
+ }
39557
+ if (verb === "pause" || verb === "resume" || verb === "destroy") {
39558
+ const id = rest[0];
39559
+ if (!id) {
39560
+ err(`corenel sessions ${verb}: missing <id>
39561
+ `);
39562
+ return 1;
39563
+ }
39564
+ const found = (await listSessions(run)).find((r2) => r2.id === id || r2.sandbox === id);
39565
+ if (!found) {
39566
+ err(`corenel sessions: no session "${id}" on this machine
39567
+ `);
39568
+ return 1;
39569
+ }
39570
+ const backend = deps.backend ?? backendFor(deps, [found.workdir], run);
39571
+ if (backend.adopt) await backend.adopt().catch(() => void 0);
39572
+ const known = !!backend.get?.(found.id);
39573
+ if (!known) {
39574
+ const r2 = await sandboxLifecycle(run, verb, found.sandbox);
39575
+ if (!r2.ok) {
39576
+ err(`corenel sessions ${verb}: ${r2.detail || "msb refused"}
39577
+ `);
39578
+ return 1;
39579
+ }
39580
+ if (verb === "destroy") {
39581
+ const still = (await listSessions(run)).find((x2) => x2.id === found.id);
39582
+ if (still) {
39583
+ err(`corenel sessions destroy: ${found.id} is still present (status ${still.status})
39584
+ `);
39585
+ return 1;
39586
+ }
39587
+ }
39588
+ out(`${verb === "destroy" ? "destroyed" : verb === "pause" ? "paused" : "resumed"} ${found.id}
39589
+ `);
39590
+ return 0;
39591
+ }
39592
+ try {
39593
+ if (verb === "pause") {
39594
+ const r2 = await backend.pause(found.id);
39595
+ if (r2 && !r2.drained) {
39596
+ err(`paused ${found.id}, but ${r2.remaining.length} call(s) were STILL RUNNING when the drain gave up
39597
+ `);
39598
+ return 1;
39599
+ }
39600
+ out(`paused ${found.id}
39601
+ `);
39602
+ return 0;
39603
+ }
39604
+ if (verb === "resume") {
39605
+ await backend.resume(found.id);
39606
+ out(`resumed ${found.id}
39607
+ `);
39608
+ return 0;
39609
+ }
39610
+ await backend.destroy(found.id);
39611
+ const still = (await listSessions(run)).find((r2) => r2.id === found.id);
39612
+ if (still) {
39613
+ err(`corenel sessions destroy: ${found.id} is still present (status ${still.status})
39614
+ `);
39615
+ return 1;
39616
+ }
39617
+ out(`destroyed ${found.id}
39618
+ `);
39619
+ return 0;
39620
+ } catch (e2) {
39621
+ err(`corenel sessions ${verb}: ${e2 instanceof Error ? e2.message : String(e2)}
39622
+ `);
39623
+ return 1;
39624
+ }
39625
+ }
39462
39626
  err(`corenel sessions: unknown subcommand "${verb}"
39463
- usage: corenel sessions [list | show <id>]
39627
+ usage: corenel sessions [list | show <id> | create [--root <dir>] | run <id> "<prompt>"
39628
+ | pause <id> | resume <id> | destroy <id>]
39464
39629
  `);
39465
39630
  return 1;
39466
39631
  }
39467
39632
 
39468
39633
  // src/args.ts
39469
39634
  import { statSync as statSync2 } from "node:fs";
39470
- import { resolve as resolve6 } from "node:path";
39635
+ import { resolve as resolve7 } from "node:path";
39471
39636
 
39472
39637
  // src/flags.ts
39473
39638
  var FLAGS = [
@@ -39651,9 +39816,9 @@ function parseArgs(argv) {
39651
39816
  else if (a2 === "--host") {
39652
39817
  out.host = argv[++i2];
39653
39818
  hostSet = true;
39654
- } else if (a2 === "--root") out.roots.push(resolve6(argv[++i2]));
39655
- else if (a2 === "--allow-add-under") out.addUnder.push(resolve6(argv[++i2]));
39656
- else if (a2 === "--clone-under") out.cloneUnder.push(resolve6(argv[++i2]));
39819
+ } else if (a2 === "--root") out.roots.push(resolve7(argv[++i2]));
39820
+ else if (a2 === "--allow-add-under") out.addUnder.push(resolve7(argv[++i2]));
39821
+ else if (a2 === "--clone-under") out.cloneUnder.push(resolve7(argv[++i2]));
39657
39822
  else if (a2 === "--allow-origin") out.allowOrigins.push(argv[++i2]);
39658
39823
  else if (a2 === "--keep-transcripts") out.keepTranscripts = true;
39659
39824
  else if (a2 === "--allow-shell") out.allowShell = true;
@@ -39664,8 +39829,8 @@ function parseArgs(argv) {
39664
39829
  } else if (a2 === "--msb") out.msbBin = argv[++i2];
39665
39830
  else if (a2 === "--guest-image") out.guestImage = argv[++i2];
39666
39831
  else if (a2 === "--allow-lan") out.allowLan = true;
39667
- else if (a2 === "--tls-cert") out.tlsCert = resolve6(argv[++i2]);
39668
- else if (a2 === "--tls-key") out.tlsKey = resolve6(argv[++i2]);
39832
+ else if (a2 === "--tls-cert") out.tlsCert = resolve7(argv[++i2]);
39833
+ else if (a2 === "--tls-key") out.tlsKey = resolve7(argv[++i2]);
39669
39834
  else if (a2 === "--self-signed") out.selfSigned = true;
39670
39835
  else if (a2 === "--tunnel") out.tunnel = argv[++i2];
39671
39836
  else if (a2 === "--tunnel-name") out.tunnelName = argv[++i2];
@@ -39680,7 +39845,7 @@ function parseArgs(argv) {
39680
39845
  else if (a2 === "--acp-auth") out.acpAuth = argv[++i2];
39681
39846
  else if (a2 === "--agent") out.acpAgentName = argv[++i2];
39682
39847
  else if (a2 === "--max-chain-depth") out.maxChainDepth = Number(argv[++i2]);
39683
- else if (a2 === "--state-dir") out.stateDir = resolve6(argv[++i2]);
39848
+ else if (a2 === "--state-dir") out.stateDir = resolve7(argv[++i2]);
39684
39849
  else if (a2 === "--name") out.name = argv[++i2];
39685
39850
  else if (a2 === "--sidecar" || a2 === "start" || a2 === "--") {
39686
39851
  } else throw new Error(`unknown argument: ${a2}
@@ -39728,12 +39893,12 @@ function assertDirectory(path, flag2) {
39728
39893
 
39729
39894
  // src/roots.ts
39730
39895
  import { promises as fs11 } from "node:fs";
39731
- import { resolve as resolve7 } from "node:path";
39896
+ import { resolve as resolve8 } from "node:path";
39732
39897
  var DISABLED = "adding working directories is not enabled (no --allow-add-under)";
39733
39898
  function createRootsController(opts) {
39734
39899
  const roots = opts.roots;
39735
- const areas = [...new Set(opts.areas.map((a2) => resolve7(a2)))];
39736
- const cloneAreas = [...new Set((opts.cloneAreas ?? []).map((a2) => resolve7(a2)))];
39900
+ const areas = [...new Set(opts.areas.map((a2) => resolve8(a2)))];
39901
+ const cloneAreas = [...new Set((opts.cloneAreas ?? []).map((a2) => resolve8(a2)))];
39737
39902
  const subs = /* @__PURE__ */ new Set();
39738
39903
  const snapshot = () => ({ root: roots[0], roots: [...roots] });
39739
39904
  async function assertInArea(abs) {
@@ -39761,8 +39926,8 @@ function createRootsController(opts) {
39761
39926
  cloneAreas,
39762
39927
  snapshot,
39763
39928
  async addFromClone(path) {
39764
- const abs = resolve7(path);
39765
- if (roots.some((r2) => resolve7(r2) === abs)) return;
39929
+ const abs = resolve8(path);
39930
+ if (roots.some((r2) => resolve8(r2) === abs)) return;
39766
39931
  if (!cloneAreas.length) throw new Error("cloning repositories is not enabled (no --clone-under)");
39767
39932
  if (!cloneAreas.some((a2) => withinRoot(a2, abs))) {
39768
39933
  throw new Error(`checkout is outside every --clone-under directory: ${abs}`);
@@ -39780,14 +39945,14 @@ function createRootsController(opts) {
39780
39945
  async browse(path) {
39781
39946
  if (!areas.length) throw new Error(DISABLED);
39782
39947
  if (path === void 0) return [...areas];
39783
- const abs = resolve7(path);
39948
+ const abs = resolve8(path);
39784
39949
  await assertInArea(abs);
39785
39950
  const entries = await fs11.readdir(abs, { withFileTypes: true });
39786
39951
  return entries.filter((e2) => e2.isDirectory()).map((e2) => e2.name);
39787
39952
  },
39788
39953
  async add(path) {
39789
- const abs = resolve7(path);
39790
- if (roots.some((r2) => resolve7(r2) === abs)) return;
39954
+ const abs = resolve8(path);
39955
+ if (roots.some((r2) => resolve8(r2) === abs)) return;
39791
39956
  await assertInArea(abs);
39792
39957
  const st2 = await fs11.stat(abs);
39793
39958
  if (!st2.isDirectory()) throw new Error(`not a directory: ${abs}`);
@@ -39806,7 +39971,7 @@ function createRootsController(opts) {
39806
39971
  // src/session/gitSource.ts
39807
39972
  import { spawn as spawn6 } from "node:child_process";
39808
39973
  import { promises as fs12 } from "node:fs";
39809
- import { join as join12, resolve as resolve8 } from "node:path";
39974
+ import { join as join12, resolve as resolve9 } from "node:path";
39810
39975
  var DEFAULT_TIMEOUT_MS3 = 10 * 6e4;
39811
39976
  var ALLOWED_SCHEMES = ["https://", "ssh://", "git+ssh://"];
39812
39977
  var SCP_LIKE = /^[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+:[A-Za-z0-9_./~-]+$/;
@@ -39892,7 +40057,7 @@ function failed(what, r2) {
39892
40057
  ${said}` : ""}`);
39893
40058
  }
39894
40059
  function createGitSource(deps) {
39895
- const areas = [...new Set(deps.areas.map((a2) => resolve8(a2)))];
40060
+ const areas = [...new Set(deps.areas.map((a2) => resolve9(a2)))];
39896
40061
  async function assertInArea(abs) {
39897
40062
  if (!areas.length) throw new Error(DISABLED2);
39898
40063
  if (!areas.some((a2) => withinRoot(a2, abs))) {
@@ -40093,8 +40258,8 @@ var JsonRpcPeer = class {
40093
40258
  return Promise.reject(new RpcError({ code: RPC_TRANSPORT_CLOSED, message: "transport closed" }));
40094
40259
  }
40095
40260
  const id = this.nextId++;
40096
- return new Promise((resolve9, reject) => {
40097
- this.pending.set(id, { resolve: resolve9, reject });
40261
+ return new Promise((resolve10, reject) => {
40262
+ this.pending.set(id, { resolve: resolve10, reject });
40098
40263
  this.write({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
40099
40264
  });
40100
40265
  }
@@ -41202,8 +41367,7 @@ async function main() {
41202
41367
  const rest = rawArgv.slice(1);
41203
41368
  const at2 = rest.indexOf("--msb");
41204
41369
  const msbBin = at2 >= 0 && rest[at2 + 1] ? rest[at2 + 1] : process.env.MSB_BIN ?? "msb";
41205
- const verbs = rest.filter((a2, i2) => !a2.startsWith("--") && !(i2 > 0 && rest[i2 - 1].startsWith("--")));
41206
- process.exitCode = await runSessionsCli(verbs, { msbBin, wsl: process.platform === "win32" });
41370
+ process.exitCode = await runSessionsCli(rest, { msbBin, wsl: process.platform === "win32" });
41207
41371
  return;
41208
41372
  }
41209
41373
  if (rawArgv[0] === "login" || rawArgv[0] === "logout" || rawArgv[0] === "auth") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corenel/sidecar",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "license": "Elastic-2.0",
6
6
  "description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
@@ -26,26 +26,25 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "scripts": {
30
- "build": "node build.mjs",
31
- "prepublishOnly": "node build.mjs",
32
- "start": "tsx src/cli.ts",
33
- "typecheck": "tsc --noEmit",
34
- "verify": "tsx src/verify.ts"
35
- },
36
29
  "dependencies": {
37
30
  "selfsigned": "^2.4.1",
38
31
  "ws": "^8.18.0"
39
32
  },
40
33
  "devDependencies": {
41
- "@corenel/protocol": "workspace:*",
42
- "@corenel/harness": "workspace:*",
43
- "@corenel/tools-node": "workspace:*",
44
- "@corenel/crew": "workspace:*",
45
34
  "@types/node": "^24.13.1",
46
35
  "@types/ws": "^8.5.13",
47
36
  "esbuild": "^0.21.5",
48
37
  "tsx": "^4.19.2",
49
- "typescript": "^5.9.3"
38
+ "typescript": "^5.9.3",
39
+ "@corenel/protocol": "0.3.2",
40
+ "@corenel/harness": "0.3.2",
41
+ "@corenel/tools-node": "0.2.0",
42
+ "@corenel/crew": "0.0.0"
43
+ },
44
+ "scripts": {
45
+ "build": "node build.mjs",
46
+ "start": "tsx src/cli.ts",
47
+ "typecheck": "tsc --noEmit",
48
+ "verify": "tsx src/verify.ts"
50
49
  }
51
- }
50
+ }