kritya 0.8.2-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +434 -0
  3. package/dist/agent/budget.js +25 -0
  4. package/dist/agent/compactor.js +78 -0
  5. package/dist/agent/contextWarning.js +6 -0
  6. package/dist/agent/killSwitch.js +111 -0
  7. package/dist/agent/loop.js +543 -0
  8. package/dist/agent/memory.js +145 -0
  9. package/dist/agent/plugins.js +408 -0
  10. package/dist/agent/skills.js +210 -0
  11. package/dist/agent/skillsCli.js +51 -0
  12. package/dist/agent/systemPrompt.js +122 -0
  13. package/dist/agent/tokens.js +55 -0
  14. package/dist/agent/toolExecutor.js +331 -0
  15. package/dist/agent/workflow.js +496 -0
  16. package/dist/agent/worktree.js +116 -0
  17. package/dist/atomicWrite.js +109 -0
  18. package/dist/audit/audit.js +230 -0
  19. package/dist/audit/cli.js +135 -0
  20. package/dist/commands/custom.js +69 -0
  21. package/dist/commands/mcpCommand.js +433 -0
  22. package/dist/commands/pluginsCommand.js +204 -0
  23. package/dist/commands/registry.js +739 -0
  24. package/dist/config/config.js +214 -0
  25. package/dist/config/debug.js +19 -0
  26. package/dist/config/models.js +82 -0
  27. package/dist/config/retention.js +24 -0
  28. package/dist/config/winAcl.js +38 -0
  29. package/dist/crash.js +102 -0
  30. package/dist/engine.js +113 -0
  31. package/dist/git/git.js +46 -0
  32. package/dist/headless.js +274 -0
  33. package/dist/hooks/hooks.js +122 -0
  34. package/dist/index.js +670 -0
  35. package/dist/lsp/client.js +413 -0
  36. package/dist/lsp/manager.js +0 -0
  37. package/dist/lsp/registry.js +62 -0
  38. package/dist/mcp/callback.js +141 -0
  39. package/dist/mcp/client.js +945 -0
  40. package/dist/mcp/login.js +117 -0
  41. package/dist/mcp/oauth.js +345 -0
  42. package/dist/mcp/servers.js +114 -0
  43. package/dist/mcp/spawnWin.js +86 -0
  44. package/dist/mcp/tokens.js +97 -0
  45. package/dist/mcp/transport.js +295 -0
  46. package/dist/net/urlSafety.js +158 -0
  47. package/dist/permissions/danger.js +96 -0
  48. package/dist/permissions/permissions.js +62 -0
  49. package/dist/permissions/rules.js +69 -0
  50. package/dist/plugins/discover.js +108 -0
  51. package/dist/plugins/mcp.js +80 -0
  52. package/dist/provider/client.js +460 -0
  53. package/dist/provider/switchyardClient.js +46 -0
  54. package/dist/provider/switchyardSidecar.js +245 -0
  55. package/dist/provider/textToolCalls.js +140 -0
  56. package/dist/repomap/repoMap.js +168 -0
  57. package/dist/repomap/symbols.js +190 -0
  58. package/dist/session/store.js +328 -0
  59. package/dist/shell/background.js +131 -0
  60. package/dist/shell/sandbox.js +396 -0
  61. package/dist/telemetry/metrics.js +172 -0
  62. package/dist/telemetry/otlp.js +128 -0
  63. package/dist/telemetry/tracer.js +240 -0
  64. package/dist/tools/askUser.js +64 -0
  65. package/dist/tools/bg.js +59 -0
  66. package/dist/tools/common.js +158 -0
  67. package/dist/tools/deepResearch.js +126 -0
  68. package/dist/tools/diff.js +70 -0
  69. package/dist/tools/document/docx.js +44 -0
  70. package/dist/tools/document/pdf.js +190 -0
  71. package/dist/tools/document/pptx.js +148 -0
  72. package/dist/tools/document/types.js +26 -0
  73. package/dist/tools/document/xlsx.js +87 -0
  74. package/dist/tools/document.js +389 -0
  75. package/dist/tools/edit.js +56 -0
  76. package/dist/tools/fetchUrl.js +218 -0
  77. package/dist/tools/fuzzyMatch.js +74 -0
  78. package/dist/tools/glob.js +34 -0
  79. package/dist/tools/grep.js +85 -0
  80. package/dist/tools/ignore.js +28 -0
  81. package/dist/tools/index.js +66 -0
  82. package/dist/tools/ls.js +27 -0
  83. package/dist/tools/lsp.js +239 -0
  84. package/dist/tools/notebook.js +215 -0
  85. package/dist/tools/read.js +32 -0
  86. package/dist/tools/repoMap.js +26 -0
  87. package/dist/tools/secretScan.js +143 -0
  88. package/dist/tools/shell.js +134 -0
  89. package/dist/tools/skills.js +69 -0
  90. package/dist/tools/subagent.js +57 -0
  91. package/dist/tools/tasks.js +43 -0
  92. package/dist/tools/webSearch.js +97 -0
  93. package/dist/tools/write.js +47 -0
  94. package/dist/tools/writeAgent.js +72 -0
  95. package/dist/trust/aiDisclosure.js +45 -0
  96. package/dist/trust/mcpTrust.js +111 -0
  97. package/dist/trust/trust.js +231 -0
  98. package/dist/types.js +1 -0
  99. package/dist/ui/AiDisclosurePrompt.js +23 -0
  100. package/dist/ui/App.js +449 -0
  101. package/dist/ui/Banner.js +71 -0
  102. package/dist/ui/ElicitationPrompt.js +46 -0
  103. package/dist/ui/Markdown.js +148 -0
  104. package/dist/ui/McpTrustPrompt.js +42 -0
  105. package/dist/ui/ModelPicker.js +19 -0
  106. package/dist/ui/PermissionPrompt.js +27 -0
  107. package/dist/ui/SelectList.js +24 -0
  108. package/dist/ui/Spinner.js +12 -0
  109. package/dist/ui/StatusLine.js +11 -0
  110. package/dist/ui/TranscriptItem.js +13 -0
  111. package/dist/ui/TrustPrompt.js +12 -0
  112. package/dist/ui/highlight.js +137 -0
  113. package/dist/ui/inline.js +241 -0
  114. package/dist/ui/mermaid.js +98 -0
  115. package/dist/ui/table.js +148 -0
  116. package/dist/ui/toolOutputPreview.js +34 -0
  117. package/dist/ui/useAgent.js +534 -0
  118. package/dist/ui/useKillSwitch.js +65 -0
  119. package/dist/ui/useSessionResume.js +39 -0
  120. package/dist/ui/useUsageBudget.js +149 -0
  121. package/dist/ui/viewport.js +71 -0
  122. package/dist/undo/undo.js +293 -0
  123. package/dist/version.js +4 -0
  124. package/package.json +122 -0
@@ -0,0 +1,111 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { CONFIG_DIR } from "../config/config.js";
5
+ import { hardenWindowsDir } from "../config/winAcl.js";
6
+ /**
7
+ * Per-server MCP trust. Workspace trust (see trust.ts) gates whether a
8
+ * project's .mcp.json is read at all, but treats it as one blob alongside
9
+ * unrelated gated content (allow rules, hooks, .env) — approving it once
10
+ * approves every server the file names, including ones a later commit or PR
11
+ * branch adds. MCP servers are their own attack surface: each is arbitrary
12
+ * code (stdio) or a remote endpoint (HTTP) that runs with the user's
13
+ * credentials the moment it's loaded. So each server also gets its own
14
+ * first-use confirmation, fingerprinted on its declared (pre-expansion)
15
+ * config — never on expanded env/header values, which may hold live secrets
16
+ * — and recorded in a global manifest/allowlist (`mcp-trusted.json`), the
17
+ * same "approve once, matched by pattern thereafter" model already used for
18
+ * shell allow rules (see permissions/rules.ts). Once approved, the identical
19
+ * server (by fingerprint) is trusted in any workspace without asking again;
20
+ * if its command, args, cwd, url, tool filter, or env/header key names change,
21
+ * the fingerprint changes and it's treated as new. Approvals are not permanent:
22
+ * `/mcp trust` lists them and `/mcp trust revoke` withdraws them, so the store
23
+ * drains as well as fills.
24
+ */
25
+ const MCP_TRUST_FILE = path.join(CONFIG_DIR, "mcp-trusted.json");
26
+ /** Stable identity for a server's declared config — structural shape only, never secret values. */
27
+ export function serverFingerprint(cfg) {
28
+ const shape = {
29
+ command: cfg.command,
30
+ args: cfg.args ?? [],
31
+ // Part of the identity, not a detail: cwd decides what a filesystem-style
32
+ // server can reach. Leaving it out would let a .mcp.json edit widen a
33
+ // server approved for ./docs to the whole disk without re-prompting.
34
+ cwd: cfg.cwd,
35
+ url: cfg.url,
36
+ envKeys: cfg.env ? Object.keys(cfg.env).sort() : [],
37
+ headerKeys: cfg.headers ? Object.keys(cfg.headers).sort() : [],
38
+ // Also scope-defining: an edit relaxing `allow` from ["search"] to ["*"]
39
+ // exposes tools the user never saw when they approved the server.
40
+ tools: cfg.tools ?? null,
41
+ };
42
+ return crypto.createHash("sha256").update(JSON.stringify(shape)).digest("hex");
43
+ }
44
+ function loadStore(storeFile) {
45
+ try {
46
+ const parsed = JSON.parse(fs.readFileSync(storeFile, "utf8"));
47
+ return Array.isArray(parsed) ? parsed : [];
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
53
+ function saveStore(storeFile, entries) {
54
+ fs.mkdirSync(path.dirname(storeFile), { recursive: true, mode: 0o700 });
55
+ hardenWindowsDir(path.dirname(storeFile));
56
+ fs.writeFileSync(storeFile, JSON.stringify(entries, null, 2) + "\n", { mode: 0o600 });
57
+ }
58
+ export function isServerTrusted(fingerprint, storeFile = MCP_TRUST_FILE) {
59
+ return loadStore(storeFile).some((e) => e.fingerprint === fingerprint);
60
+ }
61
+ /** Record a server as trusted — the manifest/allowlist other workspaces reuse. */
62
+ export function trustServer(name, fingerprint, storeFile = MCP_TRUST_FILE) {
63
+ const entries = loadStore(storeFile).filter((e) => e.fingerprint !== fingerprint);
64
+ entries.push({ name, fingerprint, trustedAt: new Date().toISOString() });
65
+ saveStore(storeFile, entries);
66
+ }
67
+ /** The full manifest, as shown by `/mcp trust`. */
68
+ export function loadMcpAllowlist(storeFile = MCP_TRUST_FILE) {
69
+ return loadStore(storeFile);
70
+ }
71
+ /**
72
+ * Withdraw trust from every entry recorded under `name`. Returns the entries
73
+ * that were dropped, so the caller can report what it actually did.
74
+ *
75
+ * An allowlist that only grows is a ratchet: approvals accumulate for servers
76
+ * the user has long since stopped using, and because trust is matched by
77
+ * fingerprint across all workspaces, a stale entry silently approves the same
78
+ * server the next time any repo declares it. Revoking is the drain.
79
+ *
80
+ * Matched by name rather than fingerprint because that is what the user has:
81
+ * the same name can hold several entries if the config changed over time, and
82
+ * "stop trusting linear" plainly means all of them.
83
+ */
84
+ export function revokeServer(name, storeFile = MCP_TRUST_FILE) {
85
+ const entries = loadStore(storeFile);
86
+ const removed = entries.filter((e) => e.name === name);
87
+ if (removed.length)
88
+ saveStore(storeFile, entries.filter((e) => e.name !== name));
89
+ return removed;
90
+ }
91
+ /** Withdraw trust from one exact config. Used when a server is removed outright. */
92
+ export function revokeFingerprint(fingerprint, storeFile = MCP_TRUST_FILE) {
93
+ const entries = loadStore(storeFile);
94
+ const rest = entries.filter((e) => e.fingerprint !== fingerprint);
95
+ if (rest.length === entries.length)
96
+ return false;
97
+ saveStore(storeFile, rest);
98
+ return true;
99
+ }
100
+ /** Split a set of declared MCP servers into already-trusted vs needing first-use confirmation. */
101
+ export function partitionByTrust(servers, storeFile = MCP_TRUST_FILE) {
102
+ const trusted = {};
103
+ const pending = {};
104
+ for (const [name, cfg] of Object.entries(servers)) {
105
+ if (isServerTrusted(serverFingerprint(cfg), storeFile))
106
+ trusted[name] = cfg;
107
+ else
108
+ pending[name] = cfg;
109
+ }
110
+ return { trusted, pending };
111
+ }
@@ -0,0 +1,231 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { CONFIG_DIR } from "../config/config.js";
5
+ import { hardenWindowsDir } from "../config/winAcl.js";
6
+ import { debugLog } from "../config/debug.js";
7
+ /**
8
+ * Workspace trust. A workspace's `.kritya/settings.json` can define `allow`
9
+ * rules (auto-approve tool calls) and `hooks` (arbitrary shell commands run
10
+ * automatically around tool calls); it can also ship a `.env` file (env vars
11
+ * merged into the process, read by every shell command and MCP server),
12
+ * `.kritya/commands/*.md` (custom slash commands — attacker-authored prompts
13
+ * run with the user's standing permissions), `.mcp.json` (MCP servers —
14
+ * arbitrary processes launched, or remote endpoints contacted with the user's
15
+ * env-expanded credentials), `KRITYA.md` (project memory — read straight into
16
+ * the system prompt on every turn, so it's the highest-leverage prompt
17
+ * injection surface a cloned repo can ship), and `.kritya/skills/*.md`
18
+ * (skills — extra instructions the model can pull in mid-session, same risk
19
+ * as a custom command). All of these take effect the
20
+ * moment kritya launches in that directory, so a cloned repo could use any of
21
+ * them to silently grant itself broad permissions or run code. Before any of
22
+ * them takes effect, the workspace must be explicitly trusted.
23
+ *
24
+ * `deny` rules are excluded from this gate — they only remove permissions,
25
+ * never grant them — and always apply regardless of trust.
26
+ *
27
+ * Trust is hash-pinned: it's recorded against the exact gated content that
28
+ * was approved. If that content changes (e.g. a later `git pull` adds a hook
29
+ * or edits `.env`), the hash no longer matches and the workspace is treated
30
+ * as untrusted again.
31
+ */
32
+ const TRUST_FILE = path.join(CONFIG_DIR, "trusted.json");
33
+ function readSettingsGatedContent(workspace) {
34
+ const file = path.join(workspace, ".kritya", "settings.json");
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(fs.readFileSync(file, "utf8"));
38
+ }
39
+ catch {
40
+ return {};
41
+ }
42
+ const allow = Array.isArray(parsed.allow)
43
+ ? parsed.allow.filter((r) => typeof r === "string")
44
+ : undefined;
45
+ const hooks = parsed.hooks && typeof parsed.hooks === "object" ? parsed.hooks : undefined;
46
+ return { allow, hooks };
47
+ }
48
+ function readEnvFile(workspace) {
49
+ try {
50
+ return fs.readFileSync(path.join(workspace, ".env"), "utf8");
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ function readCommandFiles(workspace) {
57
+ const dir = path.join(workspace, ".kritya", "commands");
58
+ let files;
59
+ try {
60
+ files = fs
61
+ .readdirSync(dir)
62
+ .filter((f) => f.endsWith(".md"))
63
+ .sort();
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ if (!files.length)
69
+ return undefined;
70
+ const commands = {};
71
+ for (const f of files) {
72
+ try {
73
+ commands[f] = fs.readFileSync(path.join(dir, f), "utf8");
74
+ }
75
+ catch {
76
+ // Unreadable file — skip it rather than fail the whole hash.
77
+ }
78
+ }
79
+ return Object.keys(commands).length ? commands : undefined;
80
+ }
81
+ function readMcpFile(workspace) {
82
+ try {
83
+ return fs.readFileSync(path.join(workspace, ".mcp.json"), "utf8");
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ }
89
+ /**
90
+ * Raw content of KRITYA.md, if present. It's read straight into the system
91
+ * prompt and followed as an instruction (see src/agent/systemPrompt.ts), so
92
+ * it's gated on trust the same as an allow rule or a hook.
93
+ */
94
+ function readMemoryFile(workspace) {
95
+ try {
96
+ return fs.readFileSync(path.join(workspace, "KRITYA.md"), "utf8");
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ }
102
+ /**
103
+ * Raw content of each .kritya/skills/<name>/SKILL.md file, keyed by the skill's
104
+ * directory name. A skill's full body is arbitrary instructions the model
105
+ * pulls in mid-session via load_skill (see src/tools/skills.ts) — same risk
106
+ * shape as a custom slash command.
107
+ */
108
+ function readSkillFiles(workspace) {
109
+ const dir = path.join(workspace, ".kritya", "skills");
110
+ let entries;
111
+ try {
112
+ entries = fs
113
+ .readdirSync(dir, { withFileTypes: true })
114
+ .filter((e) => e.isDirectory())
115
+ .map((e) => e.name)
116
+ .sort();
117
+ }
118
+ catch {
119
+ return undefined;
120
+ }
121
+ if (!entries.length)
122
+ return undefined;
123
+ const skills = {};
124
+ for (const name of entries) {
125
+ try {
126
+ skills[name] = fs.readFileSync(path.join(dir, name, "SKILL.md"), "utf8");
127
+ }
128
+ catch {
129
+ // No SKILL.md in this subdir, or unreadable — skip it rather than fail
130
+ // the whole hash.
131
+ }
132
+ }
133
+ return Object.keys(skills).length ? skills : undefined;
134
+ }
135
+ function readGatedContent(workspace) {
136
+ const { allow, hooks } = readSettingsGatedContent(workspace);
137
+ const env = readEnvFile(workspace);
138
+ const commands = readCommandFiles(workspace);
139
+ const mcp = readMcpFile(workspace);
140
+ const memory = readMemoryFile(workspace);
141
+ const skills = readSkillFiles(workspace);
142
+ if ((!allow || allow.length === 0) &&
143
+ !hooks &&
144
+ env === undefined &&
145
+ !commands &&
146
+ mcp === undefined &&
147
+ memory === undefined &&
148
+ !skills)
149
+ return null;
150
+ return { allow, hooks, env, commands, mcp, memory, skills };
151
+ }
152
+ /**
153
+ * A human-readable rendering of ALL the gated content, for the trust prompt.
154
+ * The user must be able to see everything they're approving — not just
155
+ * settings.json but the .env contents and each custom command file.
156
+ */
157
+ export function describeGatedContent(workspace) {
158
+ const { allow, hooks } = readSettingsGatedContent(workspace);
159
+ const env = readEnvFile(workspace);
160
+ const commands = readCommandFiles(workspace);
161
+ const sections = [];
162
+ if ((allow && allow.length > 0) || hooks) {
163
+ sections.push(`.kritya/settings.json (allow rules / hooks):\n${JSON.stringify({ allow, hooks }, null, 2)}`);
164
+ }
165
+ if (env !== undefined) {
166
+ sections.push(`.env (loaded into the environment of every command):\n${env.trimEnd()}`);
167
+ }
168
+ if (commands) {
169
+ const list = Object.entries(commands)
170
+ .map(([file, body]) => {
171
+ const first = body.split("\n")[0]?.trim().slice(0, 80) ?? "";
172
+ return ` /${file.replace(/\.md$/, "")} — ${first || "(empty)"}`;
173
+ })
174
+ .join("\n");
175
+ sections.push(`.kritya/commands/ (custom slash commands — prompts run as you):\n${list}`);
176
+ }
177
+ const mcp = readMcpFile(workspace);
178
+ if (mcp !== undefined) {
179
+ sections.push(`.mcp.json (MCP servers — processes launched / endpoints contacted as you):\n${mcp.trimEnd()}`);
180
+ }
181
+ const memory = readMemoryFile(workspace);
182
+ if (memory !== undefined) {
183
+ sections.push(`KRITYA.md (project memory — read into the system prompt and followed as an instruction):\n${memory.trimEnd()}`);
184
+ }
185
+ const skills = readSkillFiles(workspace);
186
+ if (skills) {
187
+ const list = Object.entries(skills)
188
+ .map(([name, body]) => {
189
+ const first = body.split("\n")[0]?.trim().slice(0, 80) ?? "";
190
+ return ` ${name} — ${first || "(empty)"}`;
191
+ })
192
+ .join("\n");
193
+ sections.push(`.kritya/skills/ (custom skills — instructions the model can load mid-session):\n${list}`);
194
+ }
195
+ return sections.join("\n\n") || "(no gated content)";
196
+ }
197
+ /**
198
+ * A stable hash of the workspace's gated content (allow rules, hooks, .env,
199
+ * and custom command files), or null if there's nothing to gate. A null
200
+ * result means trust never needs to be asked.
201
+ */
202
+ export function gatedContentHash(workspace) {
203
+ const content = readGatedContent(workspace);
204
+ if (!content)
205
+ return null;
206
+ return crypto.createHash("sha256").update(JSON.stringify(content)).digest("hex");
207
+ }
208
+ function loadTrustStore(storeFile) {
209
+ try {
210
+ const parsed = JSON.parse(fs.readFileSync(storeFile, "utf8"));
211
+ return parsed && typeof parsed === "object" ? parsed : {};
212
+ }
213
+ catch (err) {
214
+ // A missing file means "nothing trusted yet" (normal); a malformed one
215
+ // means every workspace re-prompts for trust, worth being able to see.
216
+ debugLog(`loadTrustStore(${storeFile})`, err);
217
+ return {};
218
+ }
219
+ }
220
+ /** Whether `hash` (from {@link gatedContentHash}) matches the last-trusted hash for this workspace. */
221
+ export function isTrusted(workspace, hash, storeFile = TRUST_FILE) {
222
+ return loadTrustStore(storeFile)[path.resolve(workspace)] === hash;
223
+ }
224
+ /** Record that the workspace's current gated content is trusted. */
225
+ export function saveTrust(workspace, hash, storeFile = TRUST_FILE) {
226
+ const store = loadTrustStore(storeFile);
227
+ store[path.resolve(workspace)] = hash;
228
+ fs.mkdirSync(path.dirname(storeFile), { recursive: true, mode: 0o700 });
229
+ hardenWindowsDir(path.dirname(storeFile));
230
+ fs.writeFileSync(storeFile, JSON.stringify(store, null, 2) + "\n", { mode: 0o600 });
231
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ const BLINK_INTERVAL_MS = 500;
5
+ /**
6
+ * One-time, first-run notice: kritya is an AI agent that reads/writes files
7
+ * and runs shell commands via a model you configure. Shown once per machine
8
+ * (see aiDisclosureAcknowledged in config.ts) and only on the interactive
9
+ * path — headless/CI runs skip it, since --trust already signals deliberate,
10
+ * informed use there.
11
+ */
12
+ export function AiDisclosurePrompt({ onDismiss }) {
13
+ useInput((_input, key) => {
14
+ if (key.return)
15
+ onDismiss();
16
+ });
17
+ const [blinkOn, setBlinkOn] = useState(true);
18
+ useEffect(() => {
19
+ const id = setInterval(() => setBlinkOn((v) => !v), BLINK_INTERVAL_MS);
20
+ return () => clearInterval(id);
21
+ }, []);
22
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { children: "kritya is an AI agent" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "You are interacting with an AI system: kritya reads/writes files and runs shell commands here, acting on an AI model's output \u2014 not a human." }) }), _jsx(Text, { children: "Mutating actions ask for approval first." }), _jsx(Text, { children: "Details: /help, SECURITY.md (EU AI Act Art. 50)." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Shown once. ", _jsx(Text, { color: "yellow", children: blinkOn ? "AI Disclosure" : " " })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Press Enter to continue\u2026" }) })] }));
23
+ }