failproofai 1.0.0-beta.3 → 1.0.0-beta.5
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +2 -2
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-dh5jy._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h3lfmp._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l4vuqm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0longh-._.js → [root-of-the-server]__0szqzl8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10oa-v9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18d-9oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__1p7s8-d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__10s_z4l._.js → [root-of-the-server]__094nop3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0uno39i._.js → [root-of-the-server]__0n_uvv0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__0rag5dg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0rtdfof._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ky3im9._.js → [root-of-the-server]__1gak4pd._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0jfts82.js → node_modules_html-to-image_es_index_12c_2cz.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +12 -12
- package/.next/standalone/.next/static/chunks/{29g8ykjk6sx_8.js → 0cr85wuc9gc9k.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0cvd6bcdwchc_.js → 1bkynffp3q1f7.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2uvyh8y0naofj.js → 1bw4dfzflelcv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1h0_yaoq5f3yx.js → 1tp9dex23zs8y.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/2cqk4a9s893oy.js +1 -0
- package/.next/standalone/.next/static/chunks/{13-obgukzg_ke.js → 2k285z07qce47.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0a7k_x4rp_97z.js → 2s9g1dsde_ts1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2rqm-klet_gbl.js → 2v1rpzrh2i1wv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3p-n_j80zlzi3.js → 34sjdw8lv46e3.js} +2 -2
- package/.next/standalone/Cargo.lock +3 -3
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/crates/failproofaid/src/main.rs +118 -11
- package/.next/standalone/crates/fpai-collect/src/config.rs +8 -0
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +4 -1
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +5 -1
- package/.next/standalone/crates/fpai-collect/src/spool.rs +77 -0
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +4 -1
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1 -0
- package/.next/standalone/package.json +14 -8
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +81 -28
- package/dist/worker.mjs +1 -1
- package/package.json +14 -8
- package/scripts/build-daemon-packages.mjs +210 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/prune-standalone.mjs +6 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/src/hooks/cloud-connection.ts +3 -0
- package/src/hooks/collector-config.ts +10 -0
- package/src/hooks/daemon-download.ts +137 -27
- package/src/hooks/daemon-service.ts +23 -8
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/static/chunks/01h6lfjz1_oie.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- /package/.next/standalone/.next/static/{pM1veX5Llx4YKEaiFc8Sf → LOuwMmfomO6hQYfMQYs4K}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{pM1veX5Llx4YKEaiFc8Sf → LOuwMmfomO6hQYfMQYs4K}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{pM1veX5Llx4YKEaiFc8Sf → LOuwMmfomO6hQYfMQYs4K}/_ssgManifest.js +0 -0
|
@@ -93,23 +93,26 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
93
93
|
// machine (see fpai_collect::supervisor). It observes the same `shutdown`
|
|
94
94
|
// flag the server and the cloud monitor do, so one SIGTERM stops all three.
|
|
95
95
|
//
|
|
96
|
-
// INERT until ingest is configured
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
96
|
+
// INERT until ingest is configured. But the config is not necessarily
|
|
97
|
+
// ready at startup: `failproofai config` installs the daemon and THEN
|
|
98
|
+
// connects, so the very first daemon start usually sees no ingest yet. A
|
|
99
|
+
// manager thread waits for the config to become enabled and starts the
|
|
100
|
+
// collector once it is — the collector's analogue of the cloud lane
|
|
101
|
+
// re-resolving enrolment per tick, so enabling collection takes effect
|
|
102
|
+
// within one interval with no restart and no root (see
|
|
103
|
+
// `spawn_collector_manager`). The manager owns the collector's lifecycle,
|
|
104
|
+
// including draining it on shutdown.
|
|
105
|
+
let collector_mgr = spawn_collector_manager(shutdown.clone());
|
|
100
106
|
|
|
101
107
|
let srv = server::Server::bind(&socket_path, worker)?;
|
|
102
108
|
eprintln!("[failproofaid] listening on {}", socket_path.display());
|
|
103
109
|
|
|
104
110
|
let run_result = srv.run_until(shutdown);
|
|
105
111
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
if let Some(collector) = collector {
|
|
111
|
-
collector.join_with_flush(fpai_collect::DEFAULT_FLUSH_BUDGET);
|
|
112
|
-
}
|
|
112
|
+
// Join the manager, which drains the collector within its flush budget
|
|
113
|
+
// before returning. Done before `?` so a server error still gives the
|
|
114
|
+
// collector a chance to flush instead of dropping buffered events.
|
|
115
|
+
let _ = collector_mgr.join();
|
|
113
116
|
let _ = cloud_monitor.join();
|
|
114
117
|
|
|
115
118
|
run_result?;
|
|
@@ -128,6 +131,92 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
128
131
|
/// serving the socket, because the CLI fails closed and a daemon that refused
|
|
129
132
|
/// to boot over a malformed `ingest.json` would deny every tool call on the
|
|
130
133
|
/// machine. The error is loud so it is fixable, not silent.
|
|
134
|
+
/// Own the collector's lifecycle on a dedicated thread.
|
|
135
|
+
///
|
|
136
|
+
/// The collector config is not necessarily complete when the daemon starts:
|
|
137
|
+
/// `failproofai config` installs the service, so the daemon comes up, and only
|
|
138
|
+
/// THEN runs the connect step that writes `ingest.json` and the collector
|
|
139
|
+
/// block. Resolving the config once at startup therefore left a freshly-set-up
|
|
140
|
+
/// machine shipping nothing until the next manual restart — the exact confusion
|
|
141
|
+
/// a user hit while testing.
|
|
142
|
+
///
|
|
143
|
+
/// This mirrors what the cloud-policy lane already does: it re-resolves its
|
|
144
|
+
/// config every tick precisely so `--connect` needs no root (restarting a
|
|
145
|
+
/// system unit does). The collector follows the same rule — it waits for the
|
|
146
|
+
/// config to become enabled, then starts once. Enabling collection thus takes
|
|
147
|
+
/// effect within one poll interval, no restart, no sudo.
|
|
148
|
+
///
|
|
149
|
+
/// It starts the collector once and does not tear it back down on a later
|
|
150
|
+
/// `--disconnect` — that remains a restart, matching today's behaviour and
|
|
151
|
+
/// avoiding a second start against the set-once health registry. The gap this
|
|
152
|
+
/// closes is the common one: enabled *after* startup never taking effect.
|
|
153
|
+
fn spawn_collector_manager(daemon_shutdown: Arc<AtomicBool>) -> std::thread::JoinHandle<()> {
|
|
154
|
+
let interval = collector_config_poll_interval();
|
|
155
|
+
std::thread::Builder::new()
|
|
156
|
+
.name("fpai-collect-mgr".to_string())
|
|
157
|
+
.spawn(move || {
|
|
158
|
+
// Wait until collection is enabled. A cheap config read each tick,
|
|
159
|
+
// not the full task build, so an incomplete config waits quietly
|
|
160
|
+
// rather than logging on every interval.
|
|
161
|
+
loop {
|
|
162
|
+
if daemon_shutdown.load(Ordering::Relaxed) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if collector_is_enabled() {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
let deadline = std::time::Instant::now() + interval;
|
|
169
|
+
while std::time::Instant::now() < deadline {
|
|
170
|
+
if daemon_shutdown.load(Ordering::Relaxed) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Enabled — build and start. `collector_tasks()` logs "collector
|
|
178
|
+
// enabled" once; `spawn_supervised` returns None only if the config
|
|
179
|
+
// flipped back to disabled between the check and the build, or the
|
|
180
|
+
// runtime failed to start.
|
|
181
|
+
let Some(collector) =
|
|
182
|
+
fpai_collect::spawn_supervised(collector_tasks(), daemon_shutdown.clone())
|
|
183
|
+
else {
|
|
184
|
+
return;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
while !daemon_shutdown.load(Ordering::Relaxed) {
|
|
188
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
189
|
+
}
|
|
190
|
+
collector.join_with_flush(fpai_collect::DEFAULT_FLUSH_BUDGET);
|
|
191
|
+
})
|
|
192
|
+
.expect("failed to spawn the collector manager thread")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Cheap "should the collector be running?" check — reads the two small config
|
|
196
|
+
/// files. Any error resolves to `false`; the full `collector_tasks()` build
|
|
197
|
+
/// logs the reason when it acts on an enabled config.
|
|
198
|
+
fn collector_is_enabled() -> bool {
|
|
199
|
+
let Ok(home) = paths::failproofai_home() else {
|
|
200
|
+
return false;
|
|
201
|
+
};
|
|
202
|
+
fpai_collect::config::load(&home)
|
|
203
|
+
.map(|cfg| cfg.is_enabled())
|
|
204
|
+
.unwrap_or(false)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// How often to re-check whether the collector config has become enabled.
|
|
208
|
+
/// Short by default — it is two small JSON reads — so enabling collection after
|
|
209
|
+
/// a fresh setup is prompt. `FAILPROOFAI_COLLECTOR_CONFIG_POLL_MS` overrides it.
|
|
210
|
+
fn collector_config_poll_interval() -> Duration {
|
|
211
|
+
const DEFAULT_MS: u64 = 5_000;
|
|
212
|
+
const MINIMUM_MS: u64 = 500;
|
|
213
|
+
let ms = std::env::var("FAILPROOFAI_COLLECTOR_CONFIG_POLL_MS")
|
|
214
|
+
.ok()
|
|
215
|
+
.and_then(|v| v.parse::<u64>().ok())
|
|
216
|
+
.unwrap_or(DEFAULT_MS);
|
|
217
|
+
Duration::from_millis(ms.max(MINIMUM_MS))
|
|
218
|
+
}
|
|
219
|
+
|
|
131
220
|
fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
132
221
|
let home = match paths::failproofai_home() {
|
|
133
222
|
Ok(home) => home,
|
|
@@ -211,6 +300,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
211
300
|
let spool_dir = cfg.own_spool_dir.clone();
|
|
212
301
|
let verbosity = cfg.settings.hooks_verbosity;
|
|
213
302
|
let environment = cfg.settings.environment.clone();
|
|
303
|
+
let machine_id = cfg.settings.machine_id.clone();
|
|
214
304
|
tasks.push(fpai_collect::TaskSpec::new("hook-activity", move |sd| {
|
|
215
305
|
fpai_collect::sources::hooks::run(
|
|
216
306
|
store_dir.clone(),
|
|
@@ -218,6 +308,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
218
308
|
spool_dir.clone(),
|
|
219
309
|
verbosity,
|
|
220
310
|
environment.clone(),
|
|
311
|
+
machine_id.clone(),
|
|
221
312
|
sd,
|
|
222
313
|
)
|
|
223
314
|
}));
|
|
@@ -232,6 +323,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
232
323
|
// uses, so adding one is a row here plus its own module.
|
|
233
324
|
let spool = cfg.own_spool_dir.clone();
|
|
234
325
|
let env = cfg.settings.environment.clone();
|
|
326
|
+
let machine = cfg.settings.machine_id.clone();
|
|
235
327
|
let cursors = home.join("cursors");
|
|
236
328
|
|
|
237
329
|
use fpai_collect::sources::{claude, codex, copilot, openclaw, pi};
|
|
@@ -244,6 +336,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
244
336
|
&spool,
|
|
245
337
|
&cursors,
|
|
246
338
|
&env,
|
|
339
|
+
machine.as_deref(),
|
|
247
340
|
);
|
|
248
341
|
// Subagent transcripts live under the SAME root, claimed by a second
|
|
249
342
|
// format. A separate source, not a second predicate: `is_source_file`
|
|
@@ -259,6 +352,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
259
352
|
&spool,
|
|
260
353
|
&cursors,
|
|
261
354
|
&env,
|
|
355
|
+
machine.as_deref(),
|
|
262
356
|
);
|
|
263
357
|
file_source(
|
|
264
358
|
&mut tasks,
|
|
@@ -269,6 +363,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
269
363
|
&spool,
|
|
270
364
|
&cursors,
|
|
271
365
|
&env,
|
|
366
|
+
machine.as_deref(),
|
|
272
367
|
);
|
|
273
368
|
file_source(
|
|
274
369
|
&mut tasks,
|
|
@@ -279,6 +374,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
279
374
|
&spool,
|
|
280
375
|
&cursors,
|
|
281
376
|
&env,
|
|
377
|
+
machine.as_deref(),
|
|
282
378
|
);
|
|
283
379
|
file_source(
|
|
284
380
|
&mut tasks,
|
|
@@ -289,6 +385,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
289
385
|
&spool,
|
|
290
386
|
&cursors,
|
|
291
387
|
&env,
|
|
388
|
+
machine.as_deref(),
|
|
292
389
|
);
|
|
293
390
|
file_source(
|
|
294
391
|
&mut tasks,
|
|
@@ -299,6 +396,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
299
396
|
&spool,
|
|
300
397
|
&cursors,
|
|
301
398
|
&env,
|
|
399
|
+
machine.as_deref(),
|
|
302
400
|
);
|
|
303
401
|
|
|
304
402
|
use fpai_collect::sources::{goose, hermes, opencode};
|
|
@@ -311,6 +409,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
311
409
|
&spool,
|
|
312
410
|
cursors.join("goose"),
|
|
313
411
|
&env,
|
|
412
|
+
machine.as_deref(),
|
|
314
413
|
);
|
|
315
414
|
sqlite_source(
|
|
316
415
|
&mut tasks,
|
|
@@ -321,6 +420,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
321
420
|
&spool,
|
|
322
421
|
cursors.join("opencode"),
|
|
323
422
|
&env,
|
|
423
|
+
machine.as_deref(),
|
|
324
424
|
);
|
|
325
425
|
|
|
326
426
|
// Hermes profiles are SEPARATE databases, and the SQLite poller keys its
|
|
@@ -338,6 +438,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
|
|
|
338
438
|
&spool,
|
|
339
439
|
state,
|
|
340
440
|
&env,
|
|
441
|
+
machine.as_deref(),
|
|
341
442
|
);
|
|
342
443
|
}
|
|
343
444
|
}
|
|
@@ -389,6 +490,7 @@ fn file_source(
|
|
|
389
490
|
spool_dir: &std::path::Path,
|
|
390
491
|
cursor_root: &std::path::Path,
|
|
391
492
|
environment: &str,
|
|
493
|
+
machine_id: Option<&str>,
|
|
392
494
|
) {
|
|
393
495
|
let spool_dir = spool_dir.to_path_buf();
|
|
394
496
|
// One cursor store per source, never shared: the store writes its whole map
|
|
@@ -396,6 +498,7 @@ fn file_source(
|
|
|
396
498
|
// loser would re-read from zero after every restart.
|
|
397
499
|
let state_dir = cursor_root.join(name);
|
|
398
500
|
let environment = environment.to_string();
|
|
501
|
+
let machine_id = machine_id.map(str::to_string);
|
|
399
502
|
tasks.push(fpai_collect::TaskSpec::new(name, move |sd| {
|
|
400
503
|
fpai_collect::filetail::run(
|
|
401
504
|
fpai_collect::filetail::Spec {
|
|
@@ -407,6 +510,7 @@ fn file_source(
|
|
|
407
510
|
params: fpai_collect::filetail::Params {
|
|
408
511
|
agent_id: default_agent_id.to_string(),
|
|
409
512
|
environment: environment.clone(),
|
|
513
|
+
machine_id: machine_id.clone(),
|
|
410
514
|
end_idle_mins: 10,
|
|
411
515
|
max_read_bytes: 32 * 1024 * 1024,
|
|
412
516
|
max_batch_bytes: fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
|
|
@@ -432,9 +536,11 @@ fn sqlite_source(
|
|
|
432
536
|
spool_dir: &std::path::Path,
|
|
433
537
|
state_dir: std::path::PathBuf,
|
|
434
538
|
environment: &str,
|
|
539
|
+
machine_id: Option<&str>,
|
|
435
540
|
) {
|
|
436
541
|
let spool_dir = spool_dir.to_path_buf();
|
|
437
542
|
let environment = environment.to_string();
|
|
543
|
+
let machine_id = machine_id.map(str::to_string);
|
|
438
544
|
tasks.push(fpai_collect::TaskSpec::new(name, move |sd| {
|
|
439
545
|
fpai_collect::sqlitepoll::run(
|
|
440
546
|
fpai_collect::sqlitepoll::Spec {
|
|
@@ -446,6 +552,7 @@ fn sqlite_source(
|
|
|
446
552
|
params: fpai_collect::sqlitepoll::Params {
|
|
447
553
|
agent_id: default_agent_id.to_string(),
|
|
448
554
|
environment: environment.clone(),
|
|
555
|
+
machine_id: machine_id.clone(),
|
|
449
556
|
max_rows_per_poll: 2000,
|
|
450
557
|
max_batch_bytes: fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
|
|
451
558
|
max_drain_passes: 20,
|
|
@@ -117,6 +117,13 @@ pub struct Settings {
|
|
|
117
117
|
/// here would silently drop every event this machine produced.
|
|
118
118
|
#[serde(default = "default_environment")]
|
|
119
119
|
pub environment: String,
|
|
120
|
+
/// The id of the machine this daemon runs on, written by
|
|
121
|
+
/// `failproofai config --connect --machine-id <id>`. Stamped on every
|
|
122
|
+
/// collected event so the server groups by machine rather than by
|
|
123
|
+
/// `agent_id` (a per-project identity). Absent on config written before
|
|
124
|
+
/// this field existed; such events carry no machine.
|
|
125
|
+
#[serde(default, rename = "machineId")]
|
|
126
|
+
pub machine_id: Option<String>,
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
fn default_true() -> bool {
|
|
@@ -135,6 +142,7 @@ impl Default for Settings {
|
|
|
135
142
|
hooks_verbosity: HooksVerbosity::default(),
|
|
136
143
|
redact: Redact::default(),
|
|
137
144
|
environment: default_environment(),
|
|
145
|
+
machine_id: None,
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
}
|
|
@@ -130,6 +130,8 @@ pub fn no_seed_state(_lines: &[String], _state: &mut TailState) {}
|
|
|
130
130
|
pub struct Params {
|
|
131
131
|
pub agent_id: String,
|
|
132
132
|
pub environment: String,
|
|
133
|
+
/// Machine this daemon runs on, stamped on every event. See `SpoolWriter`.
|
|
134
|
+
pub machine_id: Option<String>,
|
|
133
135
|
/// Minutes a file must be idle at EOF before `agent_end` is emitted.
|
|
134
136
|
pub end_idle_mins: u64,
|
|
135
137
|
pub max_read_bytes: u64,
|
|
@@ -248,7 +250,8 @@ async fn process_file(
|
|
|
248
250
|
spec.params.max_batch_bytes,
|
|
249
251
|
spec.format.kind,
|
|
250
252
|
&ctx.session_id,
|
|
251
|
-
)
|
|
253
|
+
)
|
|
254
|
+
.with_machine_id(spec.params.machine_id.clone());
|
|
252
255
|
let mut emitted = 0u64;
|
|
253
256
|
|
|
254
257
|
// A session with no start event never appears in the product, so retry it
|
|
@@ -81,6 +81,7 @@ pub async fn run(
|
|
|
81
81
|
spool_dir: PathBuf,
|
|
82
82
|
verbosity: HooksVerbosity,
|
|
83
83
|
environment: String,
|
|
84
|
+
machine_id: Option<String>,
|
|
84
85
|
sd: Shutdown,
|
|
85
86
|
) -> Result<(), TaskError> {
|
|
86
87
|
if verbosity == HooksVerbosity::Off {
|
|
@@ -108,6 +109,7 @@ pub async fn run(
|
|
|
108
109
|
&mut cursors,
|
|
109
110
|
verbosity,
|
|
110
111
|
&environment,
|
|
112
|
+
machine_id.as_deref(),
|
|
111
113
|
)
|
|
112
114
|
.await
|
|
113
115
|
{
|
|
@@ -138,6 +140,7 @@ async fn poll_once(
|
|
|
138
140
|
cursors: &mut CursorStore,
|
|
139
141
|
verbosity: HooksVerbosity,
|
|
140
142
|
environment: &str,
|
|
143
|
+
machine_id: Option<&str>,
|
|
141
144
|
) -> Result<u64, TaskError> {
|
|
142
145
|
let files = list_pages(store_dir).await;
|
|
143
146
|
if files.is_empty() {
|
|
@@ -149,7 +152,8 @@ async fn poll_once(
|
|
|
149
152
|
DEFAULT_MAX_BATCH_BYTES,
|
|
150
153
|
"hooks",
|
|
151
154
|
"activity",
|
|
152
|
-
)
|
|
155
|
+
)
|
|
156
|
+
.with_machine_id(machine_id.map(str::to_string));
|
|
153
157
|
// Allow rows are rolled up across the whole pass, so a bucket spanning two
|
|
154
158
|
// files still emits once.
|
|
155
159
|
let mut buckets: BTreeMap<transform::BucketKey, AllowBucket> = BTreeMap::new();
|
|
@@ -56,6 +56,14 @@ pub struct SpoolWriter {
|
|
|
56
56
|
buf_bytes: u64,
|
|
57
57
|
redact: Redact,
|
|
58
58
|
redacted: u64,
|
|
59
|
+
/// The machine this daemon runs on, stamped on every event so the server
|
|
60
|
+
/// can tell one machine from another. Without it, events carry only
|
|
61
|
+
/// `agent_id` — a per-project/per-harness identity — and the fleet views
|
|
62
|
+
/// mistake every project for a separate machine. `None` on a machine that
|
|
63
|
+
/// was never enrolled with a machine id (older config); such events simply
|
|
64
|
+
/// carry no machine and are excluded from machine-level counts rather than
|
|
65
|
+
/// guessed into one.
|
|
66
|
+
machine_id: Option<String>,
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
impl SpoolWriter {
|
|
@@ -84,6 +92,7 @@ impl SpoolWriter {
|
|
|
84
92
|
// which is the safe direction for the mistake to fall.
|
|
85
93
|
redact: Redact::Minimal,
|
|
86
94
|
redacted: 0,
|
|
95
|
+
machine_id: None,
|
|
87
96
|
}
|
|
88
97
|
}
|
|
89
98
|
|
|
@@ -95,6 +104,13 @@ impl SpoolWriter {
|
|
|
95
104
|
self
|
|
96
105
|
}
|
|
97
106
|
|
|
107
|
+
/// Set the machine id stamped on every event. An empty string is treated as
|
|
108
|
+
/// absent, so a misconfigured blank id never becomes a machine of its own.
|
|
109
|
+
pub fn with_machine_id(mut self, id: Option<String>) -> Self {
|
|
110
|
+
self.machine_id = id.filter(|s| !s.is_empty());
|
|
111
|
+
self
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
/// How many string leaves have been scrubbed. Reported without ever
|
|
99
115
|
/// logging what was removed.
|
|
100
116
|
pub fn redacted_count(&self) -> u64 {
|
|
@@ -117,6 +133,17 @@ impl SpoolWriter {
|
|
|
117
133
|
// disk.
|
|
118
134
|
self.redacted += crate::redact::scrub_value(&mut event, self.redact) as u64;
|
|
119
135
|
|
|
136
|
+
// Stamp the machine id here, at the one point every event passes
|
|
137
|
+
// through — a source cannot forget it and a new source inherits it. Set
|
|
138
|
+
// only when absent, so an event that already names its machine (a
|
|
139
|
+
// re-shipped batch, say) is never overwritten.
|
|
140
|
+
if let Some(id) = &self.machine_id
|
|
141
|
+
&& let Some(obj) = event.as_object_mut()
|
|
142
|
+
{
|
|
143
|
+
obj.entry("machine_id")
|
|
144
|
+
.or_insert_with(|| Value::String(id.clone()));
|
|
145
|
+
}
|
|
146
|
+
|
|
120
147
|
let mut line = serialize(&event)?;
|
|
121
148
|
|
|
122
149
|
if line.len() as u64 > self.max_batch_bytes {
|
|
@@ -430,6 +457,56 @@ mod tests {
|
|
|
430
457
|
std::fs::remove_dir_all(&dir).ok();
|
|
431
458
|
}
|
|
432
459
|
|
|
460
|
+
#[tokio::test]
|
|
461
|
+
async fn machine_id_is_stamped_on_every_event_when_set() {
|
|
462
|
+
// Without this the server can only group by agent_id — a per-project
|
|
463
|
+
// identity — so one machine's many projects each read as a machine.
|
|
464
|
+
let dir = tmpdir("machine-id");
|
|
465
|
+
let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
|
|
466
|
+
.with_machine_id(Some("beta-test".into()));
|
|
467
|
+
w.push(json!({"type": "agent_start", "agent_id": "claude-foo"}))
|
|
468
|
+
.await
|
|
469
|
+
.unwrap();
|
|
470
|
+
w.flush().await.unwrap();
|
|
471
|
+
let name = batches(&dir).remove(0);
|
|
472
|
+
let body = std::fs::read_to_string(dir.join(&name)).unwrap();
|
|
473
|
+
assert!(body.contains(r#""machine_id":"beta-test""#), "got {body}");
|
|
474
|
+
std::fs::remove_dir_all(&dir).ok();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#[tokio::test]
|
|
478
|
+
async fn an_absent_machine_id_stamps_nothing() {
|
|
479
|
+
// A machine that was never given an id must not invent one — the server
|
|
480
|
+
// excludes machine-less events rather than bucket them.
|
|
481
|
+
let dir = tmpdir("no-machine-id");
|
|
482
|
+
let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s");
|
|
483
|
+
w.push(json!({"type": "agent_start"})).await.unwrap();
|
|
484
|
+
w.flush().await.unwrap();
|
|
485
|
+
let name = batches(&dir).remove(0);
|
|
486
|
+
assert!(
|
|
487
|
+
!std::fs::read_to_string(dir.join(&name))
|
|
488
|
+
.unwrap()
|
|
489
|
+
.contains("machine_id")
|
|
490
|
+
);
|
|
491
|
+
std::fs::remove_dir_all(&dir).ok();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
#[tokio::test]
|
|
495
|
+
async fn an_empty_machine_id_is_treated_as_absent() {
|
|
496
|
+
let dir = tmpdir("blank-machine-id");
|
|
497
|
+
let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
|
|
498
|
+
.with_machine_id(Some(String::new()));
|
|
499
|
+
w.push(json!({"type": "agent_start"})).await.unwrap();
|
|
500
|
+
w.flush().await.unwrap();
|
|
501
|
+
let name = batches(&dir).remove(0);
|
|
502
|
+
assert!(
|
|
503
|
+
!std::fs::read_to_string(dir.join(&name))
|
|
504
|
+
.unwrap()
|
|
505
|
+
.contains("machine_id")
|
|
506
|
+
);
|
|
507
|
+
std::fs::remove_dir_all(&dir).ok();
|
|
508
|
+
}
|
|
509
|
+
|
|
433
510
|
#[tokio::test]
|
|
434
511
|
async fn redaction_can_be_turned_off() {
|
|
435
512
|
let dir = tmpdir("redact-off");
|
|
@@ -106,6 +106,8 @@ pub struct SqliteFormat {
|
|
|
106
106
|
pub struct Params {
|
|
107
107
|
pub agent_id: String,
|
|
108
108
|
pub environment: String,
|
|
109
|
+
/// Machine this daemon runs on, stamped on every event. See `SpoolWriter`.
|
|
110
|
+
pub machine_id: Option<String>,
|
|
109
111
|
pub max_rows_per_poll: u64,
|
|
110
112
|
pub max_batch_bytes: u64,
|
|
111
113
|
/// Poll passes before yielding to the shutdown check. Bounds how long a
|
|
@@ -214,7 +216,8 @@ async fn poll_once(spec: &Spec, cursors: &mut CursorStore) -> Result<(u64, bool)
|
|
|
214
216
|
spec.params.max_batch_bytes,
|
|
215
217
|
spec.format.kind,
|
|
216
218
|
spec.format.kind,
|
|
217
|
-
)
|
|
219
|
+
)
|
|
220
|
+
.with_machine_id(spec.params.machine_id.clone());
|
|
218
221
|
let emitted = outcome.events.len() as u64;
|
|
219
222
|
for event in outcome.events {
|
|
220
223
|
writer.push(event).await.map_err(io_err)?;
|
|
@@ -505,6 +505,7 @@ fn spec(root: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
505
505
|
params: Params {
|
|
506
506
|
agent_id: claude::DEFAULT_AGENT_ID.into(),
|
|
507
507
|
environment: "local".into(),
|
|
508
|
+
machine_id: None,
|
|
508
509
|
end_idle_mins: 0, // end immediately, so the test need not wait
|
|
509
510
|
max_read_bytes: 8 * 1024 * 1024,
|
|
510
511
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -268,6 +268,7 @@ fn spec(root: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
268
268
|
params: Params {
|
|
269
269
|
agent_id: claude::SUBAGENT_DEFAULT_AGENT_ID.into(),
|
|
270
270
|
environment: "local".into(),
|
|
271
|
+
machine_id: None,
|
|
271
272
|
end_idle_mins: 0,
|
|
272
273
|
max_read_bytes: 8 * 1024 * 1024,
|
|
273
274
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -725,6 +725,7 @@ fn spec(root: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
725
725
|
params: Params {
|
|
726
726
|
agent_id: codex::DEFAULT_AGENT_ID.into(),
|
|
727
727
|
environment: "local".into(),
|
|
728
|
+
machine_id: None,
|
|
728
729
|
end_idle_mins: 0, // end immediately, so the test need not wait
|
|
729
730
|
max_read_bytes: 8 * 1024 * 1024,
|
|
730
731
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -771,6 +771,7 @@ fn spec(root: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
771
771
|
params: Params {
|
|
772
772
|
agent_id: copilot::DEFAULT_AGENT_ID.into(),
|
|
773
773
|
environment: "local".into(),
|
|
774
|
+
machine_id: None,
|
|
774
775
|
end_idle_mins: 0, // end immediately, so the test need not wait
|
|
775
776
|
max_read_bytes: 8 * 1024 * 1024,
|
|
776
777
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -871,6 +871,7 @@ fn spec(db: &Path, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
871
871
|
params: Params {
|
|
872
872
|
agent_id: goose::DEFAULT_AGENT_ID.into(),
|
|
873
873
|
environment: "local".into(),
|
|
874
|
+
machine_id: None,
|
|
874
875
|
max_rows_per_poll: 500,
|
|
875
876
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
876
877
|
max_drain_passes: 8,
|
|
@@ -993,6 +993,7 @@ fn spec(db: PathBuf, spool: PathBuf, state: PathBuf) -> sqlitepoll::Spec {
|
|
|
993
993
|
params: sqlitepoll::Params {
|
|
994
994
|
agent_id: hermes::DEFAULT_AGENT_ID.into(),
|
|
995
995
|
environment: "local".into(),
|
|
996
|
+
machine_id: None,
|
|
996
997
|
max_rows_per_poll: 500,
|
|
997
998
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
998
999
|
max_drain_passes: 8,
|
|
@@ -573,6 +573,7 @@ fn spec(base: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
573
573
|
params: Params {
|
|
574
574
|
agent_id: openclaw::DEFAULT_AGENT_ID.into(),
|
|
575
575
|
environment: "local".into(),
|
|
576
|
+
machine_id: None,
|
|
576
577
|
end_idle_mins: 0, // end immediately, so the test need not wait
|
|
577
578
|
max_read_bytes: 8 * 1024 * 1024,
|
|
578
579
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -780,6 +780,7 @@ fn spec(root: PathBuf, spool: PathBuf, state: PathBuf) -> Spec {
|
|
|
780
780
|
params: Params {
|
|
781
781
|
agent_id: pi::DEFAULT_AGENT_ID.into(),
|
|
782
782
|
environment: "local".into(),
|
|
783
|
+
machine_id: None,
|
|
783
784
|
end_idle_mins: 0, // end immediately, so the test need not wait
|
|
784
785
|
max_read_bytes: 8 * 1024 * 1024,
|
|
785
786
|
max_batch_bytes: 8 * 1024 * 1024,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -76,19 +76,19 @@
|
|
|
76
76
|
"@anthropic-ai/sdk": "^0.115.0",
|
|
77
77
|
"@mdx-js/mdx": "^3.1.1",
|
|
78
78
|
"@tailwindcss/postcss": "^4.3.1",
|
|
79
|
-
"@tanstack/react-virtual": "^3.14.
|
|
79
|
+
"@tanstack/react-virtual": "^3.14.9",
|
|
80
80
|
"@testing-library/jest-dom": "^7.0.0",
|
|
81
81
|
"@testing-library/react": "^16.3.2",
|
|
82
82
|
"@testing-library/user-event": "^14.6.1",
|
|
83
|
-
"@types/node": "26.1.
|
|
83
|
+
"@types/node": "26.1.2",
|
|
84
84
|
"@types/react": "19.2.17",
|
|
85
85
|
"@types/react-dom": "^19.2.3",
|
|
86
|
-
"@vitejs/plugin-react": "^6.0.
|
|
86
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
87
87
|
"clsx": "^2.1.1",
|
|
88
88
|
"eslint": "^10.5.0",
|
|
89
89
|
"eslint-config-next": "^16.2.9",
|
|
90
|
-
"jsdom": "^30.0.
|
|
91
|
-
"lucide-react": "^1.
|
|
90
|
+
"jsdom": "^30.0.1",
|
|
91
|
+
"lucide-react": "^1.28.0",
|
|
92
92
|
"next": "^16.2.11",
|
|
93
93
|
"react": "^19.2.4",
|
|
94
94
|
"react-dom": "^19.2.4",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"dependencies": {
|
|
101
101
|
"html-to-image": "^1.11.13",
|
|
102
102
|
"html2canvas": "^1.4.1",
|
|
103
|
-
"posthog-node": "^5.
|
|
103
|
+
"posthog-node": "^5.47.7",
|
|
104
104
|
"sql.js": "^1.14.1",
|
|
105
105
|
"yaml": "^2.9.0"
|
|
106
106
|
},
|
|
@@ -108,8 +108,14 @@
|
|
|
108
108
|
"postcss": "8.5.23",
|
|
109
109
|
"eslint-plugin-react-hooks": "7.0.1",
|
|
110
110
|
"vite": "8.0.16",
|
|
111
|
-
"undici": "7.
|
|
111
|
+
"undici": "7.29.0",
|
|
112
112
|
"brace-expansion": "5.0.9",
|
|
113
113
|
"sharp": "0.35.0"
|
|
114
|
+
},
|
|
115
|
+
"optionalDependencies": {
|
|
116
|
+
"@failproofai/failproofaid-linux-x64": "1.0.0-beta.5",
|
|
117
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.0-beta.5",
|
|
118
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.0-beta.5",
|
|
119
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.0-beta.5"
|
|
114
120
|
}
|
|
115
121
|
}
|