@burdenoff/microfe-vibecontrols 2026.531.6 → 2026.531.8

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.
@@ -0,0 +1,111 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { deleteAgentConfigValue as t, getAgentConfigValue as n, setAgentConfigValue as r } from "../../services/aiApi.js";
3
+ import { useCallback as i, useEffect as a, useState as o } from "react";
4
+ import { Loader2 as s, ShieldAlert as c } from "lucide-react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
+ import { Card as d, Switch as f } from "@burdenoff/fe-libs/ui";
7
+ //#region src/components/agents/RemoteTaskPermissionsCard.tsx
8
+ function p(e) {
9
+ return [{
10
+ key: "security:vibecontrols_allow_remote_shell_task",
11
+ title: e("agentDetails.remoteShellTask.title", "Allow remote shell + script tasks"),
12
+ description: e("agentDetails.remoteShellTask.description", "Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command."),
13
+ warning: e("agentDetails.remoteShellTask.warning", "Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.")
14
+ }, {
15
+ key: "security:vibecontrols_allow_remote_file_task",
16
+ title: e("agentDetails.remoteFileTask.title", "Allow remote file writes + deletes"),
17
+ description: e("agentDetails.remoteFileTask.description", "Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only."),
18
+ warning: e("agentDetails.remoteFileTask.warning", "Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.")
19
+ }];
20
+ }
21
+ function m(e) {
22
+ if (!e) return !1;
23
+ let t = e.trim().toLowerCase();
24
+ return t === "1" || t === "true" || t === "yes";
25
+ }
26
+ function h({ agent: h }) {
27
+ let g = e(), _ = p(g), [v, y] = o(!0), [b, x] = o(null), [S, C] = o({}), [w, T] = o(null), E = i(async () => {
28
+ y(!0), T(null);
29
+ try {
30
+ let e = await Promise.all(_.map(async (e) => [e.key, m(await n(h, e.key))]));
31
+ C(Object.fromEntries(e));
32
+ } catch (e) {
33
+ T(e instanceof Error ? e.message : g("agentDetails.remoteTaskPermissions.loadFailed", "Failed to load permissions"));
34
+ } finally {
35
+ y(!1);
36
+ }
37
+ }, [h, g]);
38
+ a(() => {
39
+ E();
40
+ }, [E]);
41
+ let D = i(async (e, n) => {
42
+ x(e), T(null);
43
+ try {
44
+ n ? await r(h, e, "1") : await t(h, e), C((t) => ({
45
+ ...t,
46
+ [e]: n
47
+ }));
48
+ } catch (e) {
49
+ T(e instanceof Error ? e.message : g("agentDetails.remoteTaskPermissions.saveFailed", "Failed to save permission"));
50
+ } finally {
51
+ x(null);
52
+ }
53
+ }, [h, g]);
54
+ return /* @__PURE__ */ u(d, { children: [/* @__PURE__ */ u("div", {
55
+ className: "p-4 border-b border-border-default",
56
+ children: [/* @__PURE__ */ u("h3", {
57
+ className: "font-semibold text-text-primary flex items-center gap-2",
58
+ children: [/* @__PURE__ */ l(c, { className: "size-5" }), g("agentDetails.remoteTaskPermissions.title", "Remote Task Permissions")]
59
+ }), /* @__PURE__ */ l("p", {
60
+ className: "mt-1 text-xs text-text-secondary",
61
+ children: g("agentDetails.remoteTaskPermissions.subtitle", "These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.")
62
+ })]
63
+ }), /* @__PURE__ */ u("div", {
64
+ className: "p-4 space-y-4",
65
+ children: [w ? /* @__PURE__ */ l("div", {
66
+ className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
67
+ children: w
68
+ }) : null, v ? /* @__PURE__ */ u("div", {
69
+ className: "flex items-center gap-2 text-sm text-text-secondary",
70
+ children: [/* @__PURE__ */ l(s, { className: "size-4 animate-spin" }), g("agentDetails.remoteTaskPermissions.loading", "Loading…")]
71
+ }) : _.map((e) => {
72
+ let t = S[e.key] ?? !1, n = b === e.key;
73
+ return /* @__PURE__ */ u("div", {
74
+ className: "flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3",
75
+ children: [/* @__PURE__ */ u("div", {
76
+ className: "flex-1 min-w-0 space-y-1",
77
+ children: [
78
+ /* @__PURE__ */ l("p", {
79
+ className: "text-sm font-medium text-text-primary",
80
+ children: e.title
81
+ }),
82
+ /* @__PURE__ */ l("p", {
83
+ className: "text-xs text-text-secondary",
84
+ children: e.description
85
+ }),
86
+ e.warning ? /* @__PURE__ */ l("p", {
87
+ className: "text-xs text-status-warning-text",
88
+ children: e.warning
89
+ }) : null,
90
+ /* @__PURE__ */ l("p", {
91
+ className: "text-[11px] font-mono text-text-tertiary",
92
+ children: e.key
93
+ })
94
+ ]
95
+ }), /* @__PURE__ */ u("div", {
96
+ className: "flex items-center gap-2 flex-shrink-0",
97
+ children: [n ? /* @__PURE__ */ l(s, { className: "size-4 animate-spin text-text-secondary" }) : null, /* @__PURE__ */ l(f, {
98
+ checked: t,
99
+ onCheckedChange: (t) => void D(e.key, t),
100
+ disabled: n,
101
+ "aria-label": e.title
102
+ })]
103
+ })]
104
+ }, e.key);
105
+ })]
106
+ })] });
107
+ }
108
+ //#endregion
109
+ export { h as RemoteTaskPermissionsCard };
110
+
111
+ //# sourceMappingURL=RemoteTaskPermissionsCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteTaskPermissionsCard.js","names":[],"sources":["../../../src/components/agents/RemoteTaskPermissionsCard.tsx"],"sourcesContent":["/**\n * RemoteTaskPermissionsCard\n *\n * Toggles the per-agent \"allow remote shell tasks\" and \"allow remote\n * file-mutation tasks\" gates that the agent enforces in\n * `core/request-security.ts:denyNonLocalMutation`. The agent's task\n * plugin (POST /api/profiles/<n>/tasks/) refuses non-loopback callers by\n * default because remote shell execution + file writes are high-blast\n * radius; this card lets operators opt in per-agent without having to\n * SSH in and restart the daemon with the matching env var\n * (VIBECONTROLS_ALLOW_REMOTE_SHELL_TASK / _FILE_TASK).\n *\n * Storage: writes to the agent's encrypted config bag under\n * `security:vibecontrols_allow_remote_shell_task` and\n * `security:vibecontrols_allow_remote_file_task`. The same keys are\n * read by the agent's gate via `db.getConfig(...)`. Persisted across\n * agent restarts.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { ShieldAlert, Loader2 } from 'lucide-react';\n\nimport { Card } from '@burdenoff/fe-libs/ui';\nimport { Switch } from '@burdenoff/fe-libs/ui';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface RemoteTaskPermissionsCardProps {\n agent: AgentRef;\n}\n\ninterface GateDef {\n key: string;\n title: string;\n description: string;\n warning?: string;\n}\n\nfunction buildGates(tr: ReturnType<typeof useTr>): GateDef[] {\n return [\n {\n key: 'security:vibecontrols_allow_remote_shell_task',\n title: tr(\n 'agentDetails.remoteShellTask.title',\n 'Allow remote shell + script tasks'\n ),\n description: tr(\n 'agentDetails.remoteShellTask.description',\n 'Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command.'\n ),\n warning: tr(\n 'agentDetails.remoteShellTask.warning',\n 'Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.'\n ),\n },\n {\n key: 'security:vibecontrols_allow_remote_file_task',\n title: tr(\n 'agentDetails.remoteFileTask.title',\n 'Allow remote file writes + deletes'\n ),\n description: tr(\n 'agentDetails.remoteFileTask.description',\n 'Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only.'\n ),\n warning: tr(\n 'agentDetails.remoteFileTask.warning',\n 'Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.'\n ),\n },\n ];\n}\n\nfunction isTruthy(raw: string | null | undefined): boolean {\n if (!raw) return false;\n const v = raw.trim().toLowerCase();\n return v === '1' || v === 'true' || v === 'yes';\n}\n\nexport function RemoteTaskPermissionsCard({ agent }: RemoteTaskPermissionsCardProps) {\n const tr = useTr();\n const gates = buildGates(tr);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [values, setValues] = useState<Record<string, boolean>>({});\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n gates.map(async (g) => [g.key, isTruthy(await getAgentConfigValue(agent, g.key))] as const)\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('agentDetails.remoteTaskPermissions.loadFailed', 'Failed to load permissions')\n );\n } finally {\n setLoading(false);\n }\n // gates is stable across renders (derived from tr); intentionally not in deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, tr]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const toggle = useCallback(\n async (key: string, next: boolean) => {\n setSaving(key);\n setError(null);\n try {\n if (next) {\n await setAgentConfigValue(agent, key, '1');\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setValues((prev) => ({ ...prev, [key]: next }));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n 'agentDetails.remoteTaskPermissions.saveFailed',\n 'Failed to save permission'\n )\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <ShieldAlert className=\"size-5\" />\n {tr(\n 'agentDetails.remoteTaskPermissions.title',\n 'Remote Task Permissions'\n )}\n </h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'agentDetails.remoteTaskPermissions.subtitle',\n 'These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.'\n )}\n </p>\n </div>\n <div className=\"p-4 space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('agentDetails.remoteTaskPermissions.loading', 'Loading…')}\n </div>\n ) : (\n gates.map((g) => {\n const current = values[g.key] ?? false;\n const busy = saving === g.key;\n return (\n <div\n key={g.key}\n className=\"flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3\"\n >\n <div className=\"flex-1 min-w-0 space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">{g.title}</p>\n <p className=\"text-xs text-text-secondary\">{g.description}</p>\n {g.warning ? (\n <p className=\"text-xs text-status-warning-text\">{g.warning}</p>\n ) : null}\n <p className=\"text-[11px] font-mono text-text-tertiary\">{g.key}</p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {busy ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : null}\n <Switch\n checked={current}\n onCheckedChange={(checked) => void toggle(g.key, checked)}\n disabled={busy}\n aria-label={g.title}\n />\n </div>\n </div>\n );\n })\n )}\n </div>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import "./AgentStatusBadge.js";
2
2
  import "./AgentCard.js";
3
+ import "./RemoteTaskPermissionsCard.js";
3
4
  import "./AgentList.js";
4
5
  import "./AgentForm.js";
5
6
  import "./PluginUIPanel.js";
@@ -351,7 +351,7 @@ function _({ tabs: _, activeTabId: me, onTabSelect: he, onTabClose: ge, onOpenEx
351
351
  children: e.loading ? k("vibecontrols.sessions.terminal.startingTerminal", "Starting terminal...") : k("vibecontrols.sessions.terminal.connecting", "Connecting...")
352
352
  }), /* @__PURE__ */ h("p", {
353
353
  className: "text-text-secondary text-xs",
354
- children: e.loading ? k("vibecontrols.sessions.terminal.settingUpTmux", "Setting up tmux + ttyd") : k("vibecontrols.sessions.terminal.establishingConnection", "Establishing connection")
354
+ children: e.loading ? k("vibecontrols.sessions.terminal.settingUpWebTerminal", "Setting up session + web terminal") : k("vibecontrols.sessions.terminal.establishingConnection", "Establishing connection")
355
355
  })] })]
356
356
  })
357
357
  }),
@@ -397,7 +397,7 @@ function _({ tabs: _, activeTabId: me, onTabSelect: he, onTabClose: ge, onOpenEx
397
397
  }),
398
398
  /* @__PURE__ */ h("p", {
399
399
  className: "text-text-secondary text-xs mt-1",
400
- children: "Setting up tmux + ttyd terminal"
400
+ children: "Setting up session + web terminal"
401
401
  })
402
402
  ]
403
403
  })
@@ -837,7 +837,7 @@ function _({ tabs: _, activeTabId: me, onTabSelect: he, onTabClose: ge, onOpenEx
837
837
  children: e.loading ? k("vibecontrols.sessions.terminal.startingTerminal", "Starting terminal...") : k("vibecontrols.sessions.terminal.connecting", "Connecting...")
838
838
  }), /* @__PURE__ */ h("p", {
839
839
  className: "text-text-secondary text-xs",
840
- children: e.loading ? k("vibecontrols.sessions.terminal.settingUpTmux", "Setting up tmux + ttyd") : k("vibecontrols.sessions.terminal.establishingConnection", "Establishing connection")
840
+ children: e.loading ? k("vibecontrols.sessions.terminal.settingUpWebTerminal", "Setting up session + web terminal") : k("vibecontrols.sessions.terminal.establishingConnection", "Establishing connection")
841
841
  })] })]
842
842
  })
843
843
  }),
@@ -890,7 +890,7 @@ function _({ tabs: _, activeTabId: me, onTabSelect: he, onTabClose: ge, onOpenEx
890
890
  }),
891
891
  /* @__PURE__ */ h("p", {
892
892
  className: "text-text-secondary text-xs mt-1",
893
- children: "Setting up tmux + ttyd terminal"
893
+ children: "Setting up session + web terminal"
894
894
  })
895
895
  ]
896
896
  })