@corenel/sidecar 0.1.4 → 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.
Files changed (4) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +135 -0
  3. package/dist/cli.js +36126 -792
  4. 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.