failproofai 1.0.12-beta.1 → 1.0.12-beta.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.
- 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 +2 -2
- 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 +2 -2
- 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 +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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/_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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/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/status/route.js.nft.json +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 +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- 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.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.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.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00l2u6j86cb8y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fw0_nzkae5ao._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_1fv03uigni8s5.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -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/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 0ay__-d2h1xb5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 0tbm3vjm62h-2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 1c_jf6g5wnslk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 1my3-5p47t7r-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0jutamwqz8pg5.js → 20py8k1gd_i1m.js} +1 -1
- package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
- package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
- package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 2o6d37j425uci.js} +1 -1
- package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
- package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 42xptz88mtq3x.js} +1 -1
- package/.next/standalone/app/actions/update-jev-config.ts +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +1 -1
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +9 -18
- package/.next/standalone/fp-cloud-cli/README.md +7 -9
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1 -27
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +2 -71
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +1 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +6 -7
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
- package/.next/standalone/hermes-plugin/__init__.py +11 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +11 -10
- package/.next/standalone/sdk/python/uv.lock +31 -8
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +1686 -612
- package/dist/worker.mjs +354 -49
- package/hermes-plugin/__init__.py +11 -0
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +24 -7
- package/src/hooks/agent-roster.ts +9 -1
- package/src/hooks/agent-skill.ts +203 -0
- package/src/hooks/cloud-connection.ts +27 -51
- package/src/hooks/cloud-introspect.ts +5 -2
- package/src/hooks/cloud-login.ts +248 -0
- package/src/hooks/cloud-managed-policies.ts +54 -4
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +9 -3
- package/src/hooks/configure-wizard.ts +356 -32
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +14 -0
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/handler.ts +21 -2
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +18 -3
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +7 -9
- package/src/hooks/manager.ts +80 -46
- package/src/hooks/policy-evaluator.ts +17 -0
- package/src/hooks/policy-reviewability.ts +4 -2
- package/src/hooks/semantic/cloud-jev.ts +219 -28
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +6 -27
- package/src/hooks/semantic/jev-review.ts +90 -2
- package/src/hooks/tui.ts +280 -0
- package/src/hooks/uninstall-cli.ts +12 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
- package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
- package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
- package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
|
@@ -104,6 +104,17 @@ def _profile_name() -> str:
|
|
|
104
104
|
|
|
105
105
|
def _profile_settings_path(profile: str) -> str | None:
|
|
106
106
|
"""Evidence of the profile running this plugin, not a transcript guess."""
|
|
107
|
+
try:
|
|
108
|
+
# The host's own resolver first: it honours a per-task profile
|
|
109
|
+
# override as well as HERMES_HOME, so each profile of a multi-profile
|
|
110
|
+
# gateway reports its own config (and roster identity), not the
|
|
111
|
+
# process-wide one.
|
|
112
|
+
from hermes_constants import get_hermes_home
|
|
113
|
+
|
|
114
|
+
hosted = Path(get_hermes_home()).expanduser() / "config.yaml"
|
|
115
|
+
return str(hosted.absolute()) if hosted.is_file() else None
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
107
118
|
configured = os.environ.get("HERMES_HOME", "").strip()
|
|
108
119
|
if configured:
|
|
109
120
|
selected = Path(configured).expanduser() / "config.yaml"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.12-beta.
|
|
3
|
+
"version": "1.0.12-beta.2",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 40 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -118,22 +118,23 @@
|
|
|
118
118
|
"postcss": "8.5.26",
|
|
119
119
|
"eslint-plugin-react-hooks": "7.0.1",
|
|
120
120
|
"vite": "8.1.6",
|
|
121
|
-
"undici": "7.
|
|
121
|
+
"undici": "7.30.1",
|
|
122
|
+
"zod": "4.6.6",
|
|
122
123
|
"brace-expansion": "5.0.12",
|
|
123
124
|
"sharp": "0.35.5",
|
|
124
125
|
"source-map-js": "1.2.2",
|
|
125
126
|
"browserslist": "4.28.8"
|
|
126
127
|
},
|
|
127
128
|
"optionalDependencies": {
|
|
128
|
-
"@failproofai/failproofaid-linux-x64": "1.0.12-beta.
|
|
129
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.12-beta.
|
|
130
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.12-beta.
|
|
131
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.12-beta.
|
|
129
|
+
"@failproofai/failproofaid-linux-x64": "1.0.12-beta.2",
|
|
130
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.12-beta.2",
|
|
131
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.12-beta.2",
|
|
132
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.12-beta.2"
|
|
132
133
|
},
|
|
133
134
|
"failproofaidBinaries": {
|
|
134
|
-
"linux-x64": "
|
|
135
|
-
"linux-arm64": "
|
|
136
|
-
"darwin-x64": "
|
|
137
|
-
"darwin-arm64": "
|
|
135
|
+
"linux-x64": "4c09117fdb80d87792d1d2b77d9359cdbd0c522b5a6222ab7b927659130ddf54",
|
|
136
|
+
"linux-arm64": "dd390d76561267fc519ceb96fb90be340044e2f564bef36a44e284f227d93f29",
|
|
137
|
+
"darwin-x64": "62381f7ffe21d02289c36c4b205166b2867dbde986444d1ea185241b94fd04ef",
|
|
138
|
+
"darwin-arm64": "9ad798817d3048a5126e80dc71883538d37c1b6b6443e9c19389dc2583c8f675"
|
|
138
139
|
}
|
|
139
140
|
}
|
|
@@ -13,18 +13,35 @@ import { spawn } from "node:child_process";
|
|
|
13
13
|
const POLL_INTERVAL_MS = 150;
|
|
14
14
|
const POLL_TIMEOUT_MS = 30_000;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Whether opening a browser here can put a page in front of the person.
|
|
18
|
+
*
|
|
19
|
+
* False over SSH — `xdg-open` would start a browser on the REMOTE machine, or
|
|
20
|
+
* fail, while the person is looking at their own screen — and on a Linux box
|
|
21
|
+
* with no display at all (a container, a server). Callers then print the link,
|
|
22
|
+
* which works from any device. A guess, not a promise: a false positive costs a
|
|
23
|
+
* browser that does not appear next to a link that is printed anyway.
|
|
24
|
+
*/
|
|
25
|
+
export function canOpenBrowser(
|
|
26
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
27
|
+
platform: NodeJS.Platform = process.platform,
|
|
28
|
+
): boolean {
|
|
29
|
+
if (env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY) return false;
|
|
30
|
+
if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
/** Open a URL with the platform's default handler. Never throws. */
|
|
17
|
-
function openUrl(url: string): void {
|
|
35
|
+
export function openUrl(url: string, platform: NodeJS.Platform = process.platform): void {
|
|
18
36
|
let cmd: string;
|
|
19
37
|
let args: string[];
|
|
20
|
-
if (
|
|
38
|
+
if (platform === "darwin") {
|
|
21
39
|
cmd = "open";
|
|
22
40
|
args = [url];
|
|
23
|
-
} else if (
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
args = ["/c", "start", "", url];
|
|
41
|
+
} else if (platform === "win32") {
|
|
42
|
+
// ShellExecute via rundll32 avoids interpreting URL characters as cmd.exe syntax.
|
|
43
|
+
cmd = "rundll32.exe";
|
|
44
|
+
args = ["url.dll,FileProtocolHandler", url];
|
|
28
45
|
} else {
|
|
29
46
|
cmd = "xdg-open";
|
|
30
47
|
args = [url];
|
|
@@ -78,7 +78,15 @@ export function runtimeAgentSettingsPath(
|
|
|
78
78
|
const config = process.env.OPENCLAW_CONFIG_PATH;
|
|
79
79
|
if (config?.trim()) return resolve(config);
|
|
80
80
|
const state = process.env.OPENCLAW_STATE_DIR || process.env.OPENCLAW_HOME;
|
|
81
|
-
|
|
81
|
+
if (state?.trim()) return resolve(state, "openclaw.json");
|
|
82
|
+
// `openclaw --profile <name>` runs from `~/.openclaw-<name>`; a gateway
|
|
83
|
+
// that exports only the profile name must still resolve to that profile's
|
|
84
|
+
// config, not the default one (each profile has its own roster identity).
|
|
85
|
+
const profile = process.env.OPENCLAW_PROFILE?.trim();
|
|
86
|
+
if (profile && profile !== "default" && /^[A-Za-z0-9._-]{1,80}$/.test(profile) && !profile.startsWith(".")) {
|
|
87
|
+
return resolve(userHome, `.openclaw-${profile}`, "openclaw.json");
|
|
88
|
+
}
|
|
89
|
+
return resolve(userHome, ".openclaw", "openclaw.json");
|
|
82
90
|
}
|
|
83
91
|
const relative = USER_SETTINGS[cli];
|
|
84
92
|
if (!relative) return null;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `failproofai` skill: one small SKILL.md that `failproofai config` puts in
|
|
3
|
+
* every agent this machine has, in that agent's own user-scope skills folder.
|
|
4
|
+
*
|
|
5
|
+
* It carries no instructions of its own. It points the agent at the umbrella
|
|
6
|
+
* skill in the public FailproofAI/skills repository, which the agent reads when
|
|
7
|
+
* a FailproofAI task comes up, along with whichever focused sibling skill and
|
|
8
|
+
* `references/*.md` file the task needs. So the content is never stale on
|
|
9
|
+
* disk: changing that repository changes what every machine reads next, with
|
|
10
|
+
* no release and no re-run of setup.
|
|
11
|
+
*
|
|
12
|
+
* Every folder below was verified on 2026-10-23 against the real CLI (versions
|
|
13
|
+
* in each comment), by planting a probe skill in each candidate folder and
|
|
14
|
+
* reading what the CLI listed, or what it sent to a mock model server.
|
|
15
|
+
*
|
|
16
|
+
* Never `~/.agents/skills`, although many agents read it: Claude Code does not
|
|
17
|
+
* read it at all, Codex lists a skill found there and in `~/.codex/skills`
|
|
18
|
+
* twice, Copilot stops reading it once `COPILOT_HOME` is set, and in OpenClaw it
|
|
19
|
+
* outranks OpenClaw's own folder. Each agent's own folder works on its own.
|
|
20
|
+
*
|
|
21
|
+
* The frontmatter is shaped by the strictest of them: `name` equals the folder
|
|
22
|
+
* name (Claude Code, Cursor and Devin go by the folder, the rest by `name`, and
|
|
23
|
+
* Antigravity shows a skill without one as "SKILL"); Copilot drops a skill whose
|
|
24
|
+
* description is over 1024 characters, droid shows the model the first 200, and
|
|
25
|
+
* Hermes the first 57 — so the description is short and leads with what it is
|
|
26
|
+
* for.
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, mkdirSync, rmdirSync, rmSync } from "node:fs";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
import { join, resolve } from "node:path";
|
|
31
|
+
import { listHermesProfiles } from "../../lib/hermes-profiles";
|
|
32
|
+
import { listOpenClawProfiles } from "../../lib/openclaw-profiles";
|
|
33
|
+
import { hookLogWarn } from "./hook-logger";
|
|
34
|
+
import { writeConfigFileAtomic } from "./safe-config-write";
|
|
35
|
+
import { INTEGRATION_TYPES, type IntegrationType } from "./types";
|
|
36
|
+
|
|
37
|
+
export const AGENT_SKILL_NAME = "failproofai";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sits beside SKILL.md in a folder failproofai wrote. A `failproofai` folder
|
|
41
|
+
* without it belongs to someone else — most often the full skill installed with
|
|
42
|
+
* `npx skills add FailproofAI/skills` — and is never overwritten or removed.
|
|
43
|
+
*/
|
|
44
|
+
export const AGENT_SKILL_MARKER = ".failproofai-managed";
|
|
45
|
+
|
|
46
|
+
const SKILLS_RAW = "https://raw.githubusercontent.com/FailproofAI/skills/main/skills";
|
|
47
|
+
|
|
48
|
+
export const AGENT_SKILL_MD = `---
|
|
49
|
+
name: ${AGENT_SKILL_NAME}
|
|
50
|
+
description: FailproofAI hooks, policies, daemon, Cloud, the fp CLI and the SDK. Use for any question about setting up, running or fixing FailproofAI.
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
# FailproofAI
|
|
54
|
+
|
|
55
|
+
This skill is a pointer. Before acting on anything FailproofAI, read the full skill, which is kept current online (use your web fetch tool, or \`curl -fsSL <url>\`):
|
|
56
|
+
|
|
57
|
+
${SKILLS_RAW}/${AGENT_SKILL_NAME}/SKILL.md
|
|
58
|
+
|
|
59
|
+
Files it names by a relative path, such as \`references/cli.md\`, are in that same folder: ${SKILLS_RAW}/${AGENT_SKILL_NAME}/<path>
|
|
60
|
+
|
|
61
|
+
For focused work, read the matching skill too. Each has the same layout, with its own \`references/\` beside it.
|
|
62
|
+
|
|
63
|
+
- Writing or testing a policy: ${SKILLS_RAW}/failproofai-policy-author/SKILL.md
|
|
64
|
+
- Publishing a policy pack: ${SKILLS_RAW}/failproofai-policy-publish/SKILL.md
|
|
65
|
+
- FailproofAI Cloud and the \`fp\` CLI: ${SKILLS_RAW}/fp-cloud-cli/SKILL.md
|
|
66
|
+
- Adding the SDK to an agent, or running an evaluator: ${SKILLS_RAW}/failproofai-sdk/SKILL.md
|
|
67
|
+
- Planning evaluations: ${SKILLS_RAW}/failproofai-eval-brainstorm/SKILL.md
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
/** An agent's own override for where it keeps its files, else the default. */
|
|
71
|
+
function envDir(name: string, fallback: string): string {
|
|
72
|
+
const value = (process.env[name] ?? "").trim();
|
|
73
|
+
return value ? resolve(value) : fallback;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The skills folders `id` reads at user scope — one, except for Hermes and
|
|
78
|
+
* OpenClaw, where every profile reads only its own.
|
|
79
|
+
*/
|
|
80
|
+
export function agentSkillRoots(id: IntegrationType): string[] {
|
|
81
|
+
const home = homedir();
|
|
82
|
+
const config = envDir("XDG_CONFIG_HOME", join(home, ".config"));
|
|
83
|
+
switch (id) {
|
|
84
|
+
// 2.1.309. CLAUDE_CONFIG_DIR replaces ~/.claude rather than adding to it.
|
|
85
|
+
case "claude":
|
|
86
|
+
return [join(envDir("CLAUDE_CONFIG_DIR", join(home, ".claude")), "skills")];
|
|
87
|
+
// 0.164.0. Its own skill installer writes here too.
|
|
88
|
+
case "codex":
|
|
89
|
+
return [join(envDir("CODEX_HOME", join(home, ".codex")), "skills")];
|
|
90
|
+
// 1.0.103.
|
|
91
|
+
case "copilot":
|
|
92
|
+
return [join(envDir("COPILOT_HOME", join(home, ".copilot")), "skills")];
|
|
93
|
+
// 2026.10.22. Always under HOME: CURSOR_CONFIG_DIR does not move it.
|
|
94
|
+
case "cursor":
|
|
95
|
+
return [join(home, ".cursor", "skills")];
|
|
96
|
+
// 1.19.4.
|
|
97
|
+
case "opencode":
|
|
98
|
+
return [join(config, "opencode", "skills")];
|
|
99
|
+
// 1.4.0 and 0.73.1. Not the pi-extension package: a `skills/` folder there
|
|
100
|
+
// makes Pi stop loading the package's index.ts, and with it enforcement.
|
|
101
|
+
case "pi":
|
|
102
|
+
return [join(envDir("PI_CODING_AGENT_DIR", join(home, ".pi", "agent")), "skills")];
|
|
103
|
+
// 0.21.3. A profile reads its own skills/ and no other profile's.
|
|
104
|
+
case "hermes":
|
|
105
|
+
return listHermesProfiles().map((p) => join(p.home, "skills"));
|
|
106
|
+
// 2026.9.2. The same profile homes its hooks are installed into.
|
|
107
|
+
case "openclaw":
|
|
108
|
+
return listOpenClawProfiles().map((p) => join(p.home, "skills"));
|
|
109
|
+
// 0.248.0. FACTORY_HOME_OVERRIDE stands in for HOME.
|
|
110
|
+
case "factory":
|
|
111
|
+
return [join(envDir("FACTORY_HOME_OVERRIDE", home), ".factory", "skills")];
|
|
112
|
+
// 3000.11.205.
|
|
113
|
+
case "devin":
|
|
114
|
+
return [join(config, "devin", "skills")];
|
|
115
|
+
// 1.4.6. The documented user folder, beside the hooks.json we write; not
|
|
116
|
+
// ~/.gemini/antigravity/skills, which it does not read.
|
|
117
|
+
case "antigravity":
|
|
118
|
+
return [join(home, ".gemini", "config", "skills")];
|
|
119
|
+
// 1.55.0. GOOSE_PATH_ROOT moves Goose's whole config tree.
|
|
120
|
+
case "goose": {
|
|
121
|
+
const root = (process.env.GOOSE_PATH_ROOT ?? "").trim();
|
|
122
|
+
return [root ? join(resolve(root), "config", "skills") : join(config, "goose", "skills")];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether `id` already has a `failproofai` skill from a folder it also reads,
|
|
129
|
+
* and would list ours beside it rather than pick one. Only Codex: it reads
|
|
130
|
+
* `~/.agents/skills` (always under HOME, whatever CODEX_HOME says) as well as
|
|
131
|
+
* its own folder and lists a name found in both twice (0.164.0) — and that is
|
|
132
|
+
* where `npx skills add FailproofAI/skills -g` puts the full skill for it.
|
|
133
|
+
*/
|
|
134
|
+
function listedTwice(id: IntegrationType): boolean {
|
|
135
|
+
return id === "codex" && existsSync(join(homedir(), ".agents", "skills", AGENT_SKILL_NAME, "SKILL.md"));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write the skill for each agent in `ids`. Never fails setup: an agent whose
|
|
140
|
+
* folder cannot be written is logged and skipped. Returns the agents it was
|
|
141
|
+
* written for.
|
|
142
|
+
*/
|
|
143
|
+
export function installAgentSkill(ids: readonly IntegrationType[]): IntegrationType[] {
|
|
144
|
+
const installed: IntegrationType[] = [];
|
|
145
|
+
for (const id of ids) {
|
|
146
|
+
let wrote = false;
|
|
147
|
+
const covered = listedTwice(id);
|
|
148
|
+
for (const root of agentSkillRoots(id)) {
|
|
149
|
+
const dir = join(root, AGENT_SKILL_NAME);
|
|
150
|
+
const ours = existsSync(join(dir, AGENT_SKILL_MARKER));
|
|
151
|
+
// Covered already: a copy of ours from an earlier run is now the duplicate.
|
|
152
|
+
if (covered) {
|
|
153
|
+
if (ours) removeSkillDir(dir);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (existsSync(dir) && !ours) continue;
|
|
157
|
+
let created = false;
|
|
158
|
+
let marked = false;
|
|
159
|
+
try {
|
|
160
|
+
// Marker first: a run interrupted before it would leave a folder every
|
|
161
|
+
// later run takes for someone else's and skips.
|
|
162
|
+
mkdirSync(root, { recursive: true });
|
|
163
|
+
if (!existsSync(dir)) {
|
|
164
|
+
mkdirSync(dir);
|
|
165
|
+
created = true;
|
|
166
|
+
}
|
|
167
|
+
writeConfigFileAtomic(join(dir, AGENT_SKILL_MARKER), "", { backup: false, symlinks: "replace" });
|
|
168
|
+
marked = true;
|
|
169
|
+
writeConfigFileAtomic(join(dir, "SKILL.md"), AGENT_SKILL_MD, { backup: false, symlinks: "replace" });
|
|
170
|
+
wrote = true;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (created && !marked) {
|
|
173
|
+
try { rmdirSync(dir); } catch { /* Preserve a directory that is no longer empty. */ }
|
|
174
|
+
}
|
|
175
|
+
hookLogWarn(`the failproofai skill was not written to ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (wrote) installed.push(id);
|
|
179
|
+
}
|
|
180
|
+
return installed;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Every skill folder failproofai wrote, for any agent. */
|
|
184
|
+
export function installedAgentSkillDirs(): string[] {
|
|
185
|
+
const dirs = INTEGRATION_TYPES.flatMap((id) => agentSkillRoots(id).map((root) => join(root, AGENT_SKILL_NAME)));
|
|
186
|
+
return [...new Set(dirs)].filter((dir) => existsSync(join(dir, AGENT_SKILL_MARKER)));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Remove one skill folder; never throws, so neither setup nor uninstall stops on it. */
|
|
190
|
+
function removeSkillDir(dir: string): boolean {
|
|
191
|
+
try {
|
|
192
|
+
rmSync(dir, { recursive: true, force: true });
|
|
193
|
+
return true;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
hookLogWarn(`the failproofai skill was not removed from ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Remove every skill folder failproofai wrote. Returns the folders removed. */
|
|
201
|
+
export function removeAgentSkill(): string[] {
|
|
202
|
+
return installedAgentSkillDirs().filter(removeSkillDir);
|
|
203
|
+
}
|
|
@@ -55,7 +55,6 @@ import {
|
|
|
55
55
|
PERMISSION_POLICIES,
|
|
56
56
|
} from "./cloud-introspect";
|
|
57
57
|
import type { CloudJevConfigWrite } from "./jev-cloud-connection";
|
|
58
|
-
import { jevChecksDeclared } from "./effective-reviewers";
|
|
59
58
|
import {
|
|
60
59
|
ingestPath,
|
|
61
60
|
validateIngestKey,
|
|
@@ -141,6 +140,10 @@ export interface ConnectInput {
|
|
|
141
140
|
*/
|
|
142
141
|
sessions?: boolean;
|
|
143
142
|
environment?: string;
|
|
143
|
+
/** The org the key is already known to report into — a browser login's
|
|
144
|
+
* claim names it. Used only when introspect gives no answer (unreachable
|
|
145
|
+
* just then, or an older server), so the saved org is not left stale. */
|
|
146
|
+
org?: { id?: string; slug?: string; name?: string };
|
|
144
147
|
/** Injected by tests so nothing reaches the network. */
|
|
145
148
|
verifyPolicy?: typeof verifyCloudCredentials;
|
|
146
149
|
verifyIngest?: typeof validateIngestKey;
|
|
@@ -162,7 +165,8 @@ export interface ConnectOutcome {
|
|
|
162
165
|
* server has no introspect endpoint to say whether the key carries it.
|
|
163
166
|
*/
|
|
164
167
|
jev?: JevConnectOutcome;
|
|
165
|
-
/** Which organisation the key belongs to. Absent
|
|
168
|
+
/** Which organisation the key belongs to. Absent when neither introspect nor
|
|
169
|
+
* the caller (`ConnectInput.org`) named one. */
|
|
166
170
|
org?: { id?: string; slug?: string; name?: string };
|
|
167
171
|
/** True when at least one capability was configured. */
|
|
168
172
|
anyConfigured: boolean;
|
|
@@ -271,6 +275,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
271
275
|
// CLI ships independently of whatever server a customer runs, so a good key
|
|
272
276
|
// on an older deployment must still connect.
|
|
273
277
|
const known = identity.kind === "ok" ? identity.identity : null;
|
|
278
|
+
const org = known ? { id: known.orgId, slug: known.orgSlug, name: known.orgName } : input.org;
|
|
274
279
|
|
|
275
280
|
// Skip a probe the key provably cannot pass. This is not an optimisation: a
|
|
276
281
|
// permission failure surfaces from `/events` as a 403 whose message reads
|
|
@@ -301,9 +306,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
301
306
|
: { ok: false, reason: policyResult.reason },
|
|
302
307
|
ingest: ingestResult.ok ? { ok: true } : { ok: false, reason: ingestResult.reason },
|
|
303
308
|
anyConfigured: false,
|
|
304
|
-
org
|
|
305
|
-
? { id: known.orgId, slug: known.orgSlug, name: known.orgName }
|
|
306
|
-
: undefined,
|
|
309
|
+
org,
|
|
307
310
|
};
|
|
308
311
|
|
|
309
312
|
outcome.anyConfigured = policyResult.ok || ingestResult.ok;
|
|
@@ -327,10 +330,10 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
327
330
|
// a new URL (http → https, a new host for the same Cloud) is a reconnect,
|
|
328
331
|
// not a switch — clearing its deployment would drop enforcement until the
|
|
329
332
|
// next poll, and "Switched from X to X" says nothing true.
|
|
330
|
-
const sameKnownOrg = Boolean(prev.org?.id &&
|
|
333
|
+
const sameKnownOrg = Boolean(prev.org?.id && org?.id && prev.org.id === org.id);
|
|
331
334
|
const orgSwitch = hadConnection && !sameKnownOrg && (
|
|
332
|
-
(prev.org?.id &&
|
|
333
|
-
((!prev.org?.id || !
|
|
335
|
+
(prev.org?.id && org?.id && prev.org.id !== org.id) ||
|
|
336
|
+
((!prev.org?.id || !org?.id) && keyChanged) ||
|
|
334
337
|
(previousPolicy && !sameOrigin(previousPolicy.url)) ||
|
|
335
338
|
(prev.ingest && !sameOrigin(prev.ingest.url))
|
|
336
339
|
);
|
|
@@ -341,7 +344,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
341
344
|
if (orgSwitch) {
|
|
342
345
|
rmSync(backfillRequestPath(), { force: true });
|
|
343
346
|
const oldOrg = prev.org?.name ?? prev.org?.slug ?? prev.org?.id ?? "the previous org";
|
|
344
|
-
const newOrg =
|
|
347
|
+
const newOrg = org?.name ?? org?.slug ?? org?.id ?? "the new org";
|
|
345
348
|
changes.push(`Switched from ${oldOrg} to ${newOrg}. Nothing recorded before now will be sent to ${newOrg}; ${oldOrg}'s cloud policies stop within ~30 s.`);
|
|
346
349
|
// Remove local references, not the daemon's policy-floor state (#899).
|
|
347
350
|
clearActiveCloudManagedPolicies();
|
|
@@ -374,8 +377,8 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
374
377
|
if (prev.jev) changes.push(`Removed the previous Jev key ${maskToken(prev.jev.key)}: this key's ${PERMISSION_JEV} permission was not verified.`);
|
|
375
378
|
}
|
|
376
379
|
const since = boundary === undefined ? {} : { since: boundary };
|
|
377
|
-
if (
|
|
378
|
-
next.org = {
|
|
380
|
+
if (org) {
|
|
381
|
+
next.org = { ...org, ...since };
|
|
379
382
|
} else {
|
|
380
383
|
const { since: _drop, ...kept } = orgSwitch ? {} : (prev.org ?? {});
|
|
381
384
|
next.org = { ...kept, ...since };
|
|
@@ -385,7 +388,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
|
|
|
385
388
|
// a flush served before the daemon sees the new key retries these batches
|
|
386
389
|
// with the key that was refused, re-parking them as definitive refusals.
|
|
387
390
|
if (ingestResult.ok && prev.ingest && prev.ingest.key !== input.token) {
|
|
388
|
-
recoverCredentialRefusals(Boolean(prev.org?.id && prev.org.id ===
|
|
391
|
+
recoverCredentialRefusals(Boolean(prev.org?.id && prev.org.id === org?.id && sameOrigin(prev.ingest.url)), prev.org?.id);
|
|
389
392
|
}
|
|
390
393
|
// Legacy JSON override remains supported; the consolidated file above is
|
|
391
394
|
// published once, never as a mixture of previous and replacement slots.
|
|
@@ -551,11 +554,6 @@ export function jevLines(outcome: ConnectOutcome): string[] {
|
|
|
551
554
|
}
|
|
552
555
|
const config = jev.config;
|
|
553
556
|
if (!config || config.status === "written" || config.status === "repointed") {
|
|
554
|
-
// Plain http (loopback only) is accepted in observe mode and nowhere else:
|
|
555
|
-
// in enforce mode — set here, or by FailproofAI Cloud — Jev is OFF on this
|
|
556
|
-
// machine rather than enforcing, by the CLI and the dashboard alike.
|
|
557
|
-
const plainHttp = config !== undefined && new URL(config.baseUrl).protocol === "http:";
|
|
558
|
-
const httpLine = " Over plain http Jev runs only in observe mode: enforce (set here or by FailproofAI Cloud) turns it off on this machine. For enforce, reconnect with `failproofai config --token <key> --url https://…`.";
|
|
559
557
|
// What happened to jev.json, said first on a re-point; the mode lines
|
|
560
558
|
// below are the same for both (N1).
|
|
561
559
|
const lead =
|
|
@@ -565,44 +563,22 @@ export function jevLines(outcome: ConnectOutcome): string[] {
|
|
|
565
563
|
// deployment that arrived first): that mode and Cloud's own checks decide,
|
|
566
564
|
// not the local jev.json or the packs installed here.
|
|
567
565
|
const cloudJev = safeCloudJev();
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
...lead,
|
|
578
|
-
`${indent}on through FailproofAI Cloud, mode ${cloudJev.jevMode} (set by FailproofAI Cloud); the Jev checks it deployed here run on FailproofAI Cloud.`,
|
|
579
|
-
];
|
|
580
|
-
}
|
|
581
|
-
// No Cloud deployment has been read on this machine yet (a first connect,
|
|
582
|
-
// or the first after a disconnect): FailproofAI Cloud's Jev mode, when it
|
|
583
|
-
// sets one, arrives with that deployment and overrides jev.json — so the
|
|
584
|
-
// local mode is said as the fallback it is, not as a promise (N3).
|
|
585
|
-
const localMode = config?.status === "repointed" ? config.mode : "observe";
|
|
586
|
-
if (localMode === "off") {
|
|
587
|
-
return [...lead, `${indent}switched off in ${config?.path ?? "jev.json"} (mode off). FailproofAI Cloud's Jev mode, if it sets one, arrives with its first deployment. To turn it on: \`failproofai jev setup --mode observe\`.`];
|
|
588
|
-
}
|
|
589
|
-
return [
|
|
590
|
-
...lead,
|
|
591
|
-
`${indent}configured through FailproofAI Cloud; waiting for FailproofAI Cloud's Jev mode, which arrives with this machine's first deployment (within ~30 s) and decides. Until then, and if Cloud sets none: ${localMode} (${config?.path ?? "jev.json"}). Check with \`failproofai jev status\`.`,
|
|
592
|
-
// The package ships no Jev checks, so local "on" asks nothing until a
|
|
593
|
-
// pack gives it some. Checks FailproofAI Cloud deploys run on Cloud.
|
|
594
|
-
...(jevChecksDeclared()
|
|
595
|
-
? []
|
|
596
|
-
: [" No Jev checks are installed on this machine; checks FailproofAI Cloud deploys here run on Cloud. To add local ones: `failproofai policies add FailproofAI/jev-policies`."]),
|
|
597
|
-
...(plainHttp ? [httpLine] : []),
|
|
598
|
-
];
|
|
566
|
+
// One line: FailproofAI Cloud decides the mode, whatever jev.json says, so
|
|
567
|
+
// the local fallback and the packs installed here are not worth a line.
|
|
568
|
+
const state =
|
|
569
|
+
cloudJev.jevMode === null
|
|
570
|
+
? "waiting for its first deployment"
|
|
571
|
+
: cloudJev.jevMode === "off"
|
|
572
|
+
? "off — no Jev policy deployed"
|
|
573
|
+
: cloudJev.jevMode;
|
|
574
|
+
return [...lead, `${indent}managed by FailproofAI Cloud · ${state}`];
|
|
599
575
|
}
|
|
600
576
|
if (config.status === "kept") {
|
|
601
577
|
const lines = [` Jev key stored; ${config.path} already exists and was left as configured.`];
|
|
602
578
|
if (config.otherOrigin) {
|
|
603
579
|
lines.push(
|
|
604
580
|
config.jevOff?.why === "off"
|
|
605
|
-
? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here
|
|
581
|
+
? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here: \`failproofai jev setup --provider failproofai\`.`
|
|
606
582
|
: ` It points at ${config.otherOrigin}, so Jev stays off until you run \`failproofai jev setup --provider failproofai\`.`,
|
|
607
583
|
);
|
|
608
584
|
} else if (config.jevOff?.why === "refused") {
|
|
@@ -610,11 +586,11 @@ export function jevLines(outcome: ConnectOutcome): string[] {
|
|
|
610
586
|
` It was refused (${config.jevOff.problem}), so Jev is off. ${config.jevOff.fix ? `Fix: \`${config.jevOff.fix}\`.` : "Write a valid one: `failproofai jev setup --provider failproofai`."}`,
|
|
611
587
|
);
|
|
612
588
|
} else if (config.jevOff?.why === "off") {
|
|
613
|
-
lines.push(" It has Jev switched off (mode off)
|
|
589
|
+
lines.push(" It has Jev switched off (mode off) until FailproofAI Cloud's Jev mode arrives with this machine's first deployment; from then on FailproofAI Cloud decides.");
|
|
614
590
|
}
|
|
615
591
|
if (jev.stillOn) {
|
|
616
592
|
lines.push(
|
|
617
|
-
` Jev is still on through FailproofAI Cloud (${jev.stillOn} mode): it sends each checked tool call and the recent prompt to FailproofAI Cloud.
|
|
593
|
+
` Jev is still on through FailproofAI Cloud (${jev.stillOn} mode): it sends each checked tool call and the recent prompt to FailproofAI Cloud. FailproofAI Cloud sets its mode: off when no Jev policy is deployed here.`,
|
|
618
594
|
);
|
|
619
595
|
}
|
|
620
596
|
return lines;
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
* second.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
import { displayText } from "./cloud-login";
|
|
37
|
+
|
|
36
38
|
/** The path introspect is served on, appended to the cloud origin. */
|
|
37
39
|
export const INTROSPECT_PATH = "/v1/auth/introspect";
|
|
38
40
|
|
|
@@ -126,8 +128,9 @@ export async function introspectKey(
|
|
|
126
128
|
identity: {
|
|
127
129
|
keyId: typeof b.key_id === "string" ? b.key_id : undefined,
|
|
128
130
|
orgId: typeof b.org_id === "string" ? b.org_id : undefined,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
// Printed in "Connected as …": text only, never terminal controls.
|
|
132
|
+
orgSlug: displayText(b.org_slug),
|
|
133
|
+
orgName: displayText(b.org_name),
|
|
131
134
|
permissions: b.permissions.filter((p): p is string => typeof p === "string"),
|
|
132
135
|
expiresAt: typeof b.expires_at === "string" ? b.expires_at : null,
|
|
133
136
|
otelIngest: typeof b.otel_ingest === "boolean" ? b.otel_ingest : undefined,
|