@corenel/sidecar 0.1.5 → 0.1.6
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/CHANGELOG.md +76 -0
- package/README.md +135 -0
- package/dist/cli.js +36070 -854
- package/package.json +5 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
The daemon release. `@corenel/sidecar` could already expose a machine's files and
|
|
6
|
+
shell to a browser session; it can now also **run crew agents on its own** —
|
|
7
|
+
on a schedule, when files change, or when another run finishes — with budgets,
|
|
8
|
+
permissions and per-owner isolation enforced node-side.
|
|
9
|
+
|
|
10
|
+
Everything below is additive. An existing `pd sidecar` setup behaves as it did in
|
|
11
|
+
0.1.5 unless you pass `--daemon`.
|
|
12
|
+
|
|
13
|
+
### Daemon core
|
|
14
|
+
- `--daemon` starts an always-on core: an interruptible clock (sleep-until-next-wake,
|
|
15
|
+
wake signal, max-sleep cap) driving a trigger dispatcher over a shared runner.
|
|
16
|
+
- Node run executor built on `runAgent`, with session recording to disk.
|
|
17
|
+
- Per-agent run queue — one live run per agent, so a slow run cannot stack.
|
|
18
|
+
- Node gateway client (absolute endpoint, node auth); daemon-origin requests are
|
|
19
|
+
tagged so entitlement metering can tell them from browser traffic.
|
|
20
|
+
- `--default-model` fallback; a crew agent with no model fails fast rather than
|
|
21
|
+
starting an unrunnable run.
|
|
22
|
+
|
|
23
|
+
### Triggers
|
|
24
|
+
- Minimal 5-field UTC cron matcher with per-agent trigger state (`lastFired`,
|
|
25
|
+
`nextWake`), persisted and serialized so concurrent writes cannot clobber it.
|
|
26
|
+
- File-change triggers: a self-contained glob matcher, a bounded change buffer,
|
|
27
|
+
and an idempotent re-scan.
|
|
28
|
+
- Run-complete triggers: an evaluator carrying owner, stop reason and run
|
|
29
|
+
generation, with a chain-depth cap (`--max-chain-depth`, validated so a bad
|
|
30
|
+
value cannot disable the cap) to stop runs triggering each other forever.
|
|
31
|
+
- `schedule_self` tool for self-directed wakes.
|
|
32
|
+
|
|
33
|
+
### Budgets
|
|
34
|
+
- Run budgets enforced node-side (stop / degrade), failing **closed** on an
|
|
35
|
+
unpriceable run rather than proceeding unmetered.
|
|
36
|
+
- A continuous trigger cannot become due without a budget cap.
|
|
37
|
+
- Pure usage pricing extracted to the harness; the node side hydrates a price table.
|
|
38
|
+
|
|
39
|
+
### Permissions and attendance
|
|
40
|
+
- Unattended prompt resolver: deny, park (bounded by the real budget remainder),
|
|
41
|
+
or allow — an abort resolves to null rather than a silent deny.
|
|
42
|
+
- Attendance registry: asks are broadcast to attached clients, first answer wins,
|
|
43
|
+
and replay on attach means reconnecting does not lose a pending ask.
|
|
44
|
+
- Pending-ask records with boot reconciliation (audit-only) and permission audit lines.
|
|
45
|
+
- Stall watchdog aborts hung runs while leaving parked ones alone.
|
|
46
|
+
|
|
47
|
+
### Multi-owner isolation
|
|
48
|
+
- Per-owner run state and owner-qualified dispatch keys; a multi-root schedule
|
|
49
|
+
enumerates the daemon's own crew plus every `hosts/<dir>/crew`.
|
|
50
|
+
- `call_agent` is confined to the caller's owner, and an attended run resolves its
|
|
51
|
+
owner from the authenticated identity rather than a client-supplied value.
|
|
52
|
+
|
|
53
|
+
### Hosts and sync
|
|
54
|
+
- Durable `sidecarId` with friendly-name resolution; the CLI advertises id and name.
|
|
55
|
+
- `hosts.json` manifest: sanitized frozen `dirName`, null-prototype parsing,
|
|
56
|
+
per-record validation, atomic writes and serialized read-modify-write.
|
|
57
|
+
- `pd sidecar hosts list` / `forget`; boot logs manifest drift.
|
|
58
|
+
- Host-sync service with daemon-derived destinations. Writes to a synced tree go
|
|
59
|
+
through `syncWrite`/`syncRemove` only — the exposed surface has no `write`/`remove`
|
|
60
|
+
to bypass, and path confinement is reused from `NodeFileService` rather than
|
|
61
|
+
reimplemented.
|
|
62
|
+
- A materialized directory is never reclaimed by eviction.
|
|
63
|
+
|
|
64
|
+
### Also
|
|
65
|
+
- `--help` / `-h` now print usage and exit 0. They were previously rejected as
|
|
66
|
+
unknown arguments, which printed a bare flag list and exited 1. The help text is
|
|
67
|
+
generated from the same table the parser validates against, so a flag cannot be
|
|
68
|
+
added without appearing in `--help`.
|
|
69
|
+
- `fs_delete` tool, wired to `SidecarFileService.remove`.
|
|
70
|
+
- Quieter cloudflared output.
|
|
71
|
+
- A second `hello` is ignored once authenticated.
|
|
72
|
+
|
|
73
|
+
### Housekeeping
|
|
74
|
+
- The package's own `typecheck` now passes. It shares modules with the browser
|
|
75
|
+
harness (which guards its browser bits at runtime), so the tsconfig lacked the
|
|
76
|
+
DOM *types* and reported a dozen phantom errors against correct code.
|
package/README.md
CHANGED
|
@@ -29,6 +29,128 @@ match the site you're connecting from (the app fills it in for you).
|
|
|
29
29
|
| `--host <addr>` | `127.0.0.1` | Bind address. Non-loopback is reachable off-box; keep the token secret. |
|
|
30
30
|
| `--tls-cert <f>` `--tls-key <f>` | _(none)_ | Serve `wss://` (needed for a remote sidecar from an https page). |
|
|
31
31
|
|
|
32
|
+
## Daemon (preview)
|
|
33
|
+
|
|
34
|
+
`--daemon` also runs crew agents in-process, so the app can command a manual run on
|
|
35
|
+
your machine instead of only serving tools. It also runs an always-on clock: agents
|
|
36
|
+
with a `schedule` or `continuous` trigger fire on their own, with no client
|
|
37
|
+
connected, over the same shared runner (and its per-agent queue) a client-commanded
|
|
38
|
+
run uses. Global-scope agents only.
|
|
39
|
+
|
|
40
|
+
New flag: `--default-model <id>` — a fallback model id used for a crew agent whose
|
|
41
|
+
`settings.model` is unset. With neither set, the daemon fails that agent's run
|
|
42
|
+
fast, before any network call, instead of sending an empty model id to the gateway.
|
|
43
|
+
|
|
44
|
+
**Budget enforcement.** The daemon enforces the agent's `budget` on every run,
|
|
45
|
+
headless or client-commanded:
|
|
46
|
+
|
|
47
|
+
- `maxTokens` and `maxMs` are always enforceable — no pricing needed.
|
|
48
|
+
- `maxUsd` additionally requires the daemon to be able to *price* the run's model.
|
|
49
|
+
The daemon hydrates a price table from the backend (`GET <endpoint>/llm-providers/available`)
|
|
50
|
+
once at startup using the daemon's login token (see "Log in" below). If a `maxUsd`
|
|
51
|
+
cap is set on a model the daemon cannot price, the run **refuses to start** rather
|
|
52
|
+
than let the cap silently never trip.
|
|
53
|
+
- With no attendance relay yet, a breach's `ask`/`warn` action collapses to `stop`
|
|
54
|
+
for an unattended run (there is nobody to ask).
|
|
55
|
+
- **A breach always results in `stop` for a daemon run in this release.** Saved crew
|
|
56
|
+
agents get `onBreach: 'ask'` (which collapses to `stop`) and the daemon supplies no
|
|
57
|
+
cheaper-model resolver, so `degrade` is unreachable in production — it is
|
|
58
|
+
implemented and tested as a seam only, pending a real `CrewBudget.onBreach` field.
|
|
59
|
+
|
|
60
|
+
**`continuous` triggers require a budget.** A `continuous` trigger with neither
|
|
61
|
+
`budget.maxUsd` nor `budget.maxTokens` set is refused — otherwise it would fire on
|
|
62
|
+
every clock pass, forever. `continuous` also has a 60-second minimum interval
|
|
63
|
+
(`trigger.config.minIntervalMs`, floored at 60000) regardless of what's configured.
|
|
64
|
+
|
|
65
|
+
**`schedule` triggers use UTC cron.** The 5-field cron expression
|
|
66
|
+
(`minute hour day-of-month month day-of-week`) is matched against the machine's UTC
|
|
67
|
+
clock. There is no local-time or DST handling — write the expression in UTC.
|
|
68
|
+
|
|
69
|
+
**Permission governance (`ask`-tier tools, unattended runs).** A crew agent's
|
|
70
|
+
`POLICY.yaml` may declare `unattended: deny | park | allow` for what a daemon run
|
|
71
|
+
does with an `ask`-tier permission gate when nobody is watching (default `deny`):
|
|
72
|
+
|
|
73
|
+
- `deny` — the ask resolves to deny immediately. The run continues degraded (a
|
|
74
|
+
denial is not a run failure); this is the only outcome that never waits.
|
|
75
|
+
- `allow` — the ask resolves to allow immediately, unattended.
|
|
76
|
+
- `park` — the run WAITS for someone to attend and answer, bounded by
|
|
77
|
+
`min(remaining budget.maxMs, parkTimeoutMs ?? 30 minutes)` (`PARK_TIMEOUT_MS`,
|
|
78
|
+
overridable per agent via `POLICY.parkTimeoutMs`). If the bound expires with
|
|
79
|
+
nobody answering, the ask resolves to deny and the run continues degraded — the
|
|
80
|
+
same outcome as `deny`, just delayed. A parked run **holds its agent's queue
|
|
81
|
+
slot** for the whole window: another run for the same agent cannot start until
|
|
82
|
+
the park ends (answered, expired, or the run is otherwise stopped).
|
|
83
|
+
- If a client IS attending when the ask fires (any non-`park` mode too), it is
|
|
84
|
+
offered the chance to answer directly before the unattended mode applies; an
|
|
85
|
+
attended ask that goes unanswered (e.g. every client detaches) falls through to
|
|
86
|
+
the unattended mode above.
|
|
87
|
+
|
|
88
|
+
Approvals are answered from the app's **pending-approvals surface** — a
|
|
89
|
+
connected browser (or CLI) client attached to the daemon's shared attendance
|
|
90
|
+
registry sees every outstanding ask (including ones that started before it
|
|
91
|
+
connected) and can answer any of them.
|
|
92
|
+
|
|
93
|
+
**A daemon restart destroys parked runs.** Parked state lives only in the
|
|
94
|
+
daemon's process memory (the in-flight `await`, not a durable record) — a
|
|
95
|
+
restart drops it. The run's pending record is marked dead on the next boot;
|
|
96
|
+
there is no resume. Re-issue the run after restarting if it still needs to
|
|
97
|
+
happen.
|
|
98
|
+
|
|
99
|
+
**The stall watchdog never aborts a parked run.** A run idle for 10 minutes
|
|
100
|
+
(no event delivered to any caller) is aborted with `stopReason: 'stalled'` —
|
|
101
|
+
except a run that is currently parked, which is legitimately silent while it
|
|
102
|
+
waits for a human and is explicitly excluded from the watchdog's scan.
|
|
103
|
+
|
|
104
|
+
Manual smoke (schedule trigger, no browser open):
|
|
105
|
+
|
|
106
|
+
1. Create a crew agent with a schedule trigger and a token budget, e.g. in its
|
|
107
|
+
`AGENT.md` frontmatter:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
triggers:
|
|
111
|
+
- type: schedule
|
|
112
|
+
config: { cron: "*/5 * * * *" }
|
|
113
|
+
budget:
|
|
114
|
+
maxTokens: 2000
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
2. Start the daemon: `npx @corenel/sidecar --daemon --root <project>`.
|
|
118
|
+
3. Wait for the next 5-minute UTC boundary and confirm a new session appears
|
|
119
|
+
under `~/.prompd/crew/<name>/sessions/` — with no browser open and no client
|
|
120
|
+
connected, proving the clock (not a client command) drove the run.
|
|
121
|
+
|
|
122
|
+
Manual smoke (manual/client-commanded trigger):
|
|
123
|
+
|
|
124
|
+
1. **Log in** with the node CLI so the daemon has a gateway token:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
corenel login
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This writes the token to `<state dir>/token` (default `~/.corenel/token`, or
|
|
131
|
+
`~/.prompd/token` in a `.prompd`-branded install).
|
|
132
|
+
|
|
133
|
+
2. **Create a crew agent** in the app (global scope) so it lands on disk at
|
|
134
|
+
`<state dir>/crew/<name>/`.
|
|
135
|
+
|
|
136
|
+
3. **Start the sidecar with `--daemon`:**
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npx @corenel/sidecar --daemon --allow-origin https://prompd.app
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Add `--endpoint <url>` to point at a non-default gateway, `--state-dir <dir>`
|
|
143
|
+
to use a state dir other than the default, or `--default-model <id>` to give
|
|
144
|
+
agents with no `settings.model` a fallback.
|
|
145
|
+
|
|
146
|
+
4. **Pair from the app** with the printed token, then command a manual run of the
|
|
147
|
+
agent you created. Confirm the reply streams back in the app as it's generated,
|
|
148
|
+
and that a new session appears under `<state dir>/crew/<name>/sessions/<runId>/`.
|
|
149
|
+
|
|
150
|
+
Entitlement for daemon runs is enforced at the gateway, not locally — a 401/403 on
|
|
151
|
+
the run means the login token from step 1 doesn't carry daemon access, not a bug in
|
|
152
|
+
the sidecar itself.
|
|
153
|
+
|
|
32
154
|
## Security
|
|
33
155
|
|
|
34
156
|
- Loopback by default; a browser connection requires both the pairing token **and** an
|
|
@@ -36,5 +158,18 @@ match the site you're connecting from (the app fills it in for you).
|
|
|
36
158
|
- The HTTP proxy (for local LLMs) only reaches **loopback** hosts — it can't be used as
|
|
37
159
|
an SSRF pivot to internal services.
|
|
38
160
|
- `sidecar_shell_exec` is off unless you pass `--allow-shell`.
|
|
161
|
+
- **Pairing a device grants its agents real reach on this machine.** With
|
|
162
|
+
`--daemon` and host config sync, a paired client can sync its own crew
|
|
163
|
+
agents to the daemon and have them run here — on the daemon's machine,
|
|
164
|
+
against the daemon's own `--root` workspace, not a workspace of the
|
|
165
|
+
client's own. Each synced agent runs under the POLICY the client itself
|
|
166
|
+
authored for it, including `unattended: allow`, on a schedule the client
|
|
167
|
+
itself sets. If you also pass `--allow-shell`, that shell access extends
|
|
168
|
+
to a paired client's agents too, exactly as it does to a client-commanded
|
|
169
|
+
run. There is no separate approval step per agent or per run beyond the
|
|
170
|
+
pairing handshake — treat pairing a device the same way you'd treat
|
|
171
|
+
handing it an SSH key to this machine: only pair devices you trust with
|
|
172
|
+
that level of access, and un-pair (revoke) a device the moment you stop
|
|
173
|
+
trusting it.
|
|
39
174
|
|
|
40
175
|
Licensed under Elastic-2.0.
|