kritya 0.8.4-beta → 0.8.6-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.
@@ -45,6 +45,13 @@ export class Agent {
45
45
  dryRunMode = false;
46
46
  /** When true, file-edit tools auto-approve without prompting (see ACCEPT_EDITS_TOOL_NAMES). */
47
47
  acceptEdits = false;
48
+ /**
49
+ * Whether a human can see and respond to a permission prompt right now.
50
+ * True by default (the CLI and its subagents render one); headless runs
51
+ * set this false because there is no one to answer it, so a forced
52
+ * warning prompt there must resolve on its own rather than block forever.
53
+ */
54
+ interactive = true;
48
55
  /** Fires each time a tool call is auto-approved because of acceptEdits, for a UI counter. */
49
56
  onAutoApprove;
50
57
  /**
@@ -1,4 +1,5 @@
1
1
  import { classifyDanger } from "../permissions/danger.js";
2
+ import { acknowledgeUnsandboxedFallback, sandboxFallbackWarning } from "../shell/sandbox.js";
2
3
  import { isPlanningDocWrite, loadProjectState } from "./workflow.js";
3
4
  /** How much tool output to hand the UI (it shows a preview and expands on toggle). */
4
5
  const PREVIEW_CHARS = 4000;
@@ -190,7 +191,16 @@ export class ToolExecutor {
190
191
  return "This action is blocked by a deny rule in the user's settings. Do not retry it; take a different approach.";
191
192
  }
192
193
  // Destructive shell commands always prompt with a warning, even if allowlisted.
193
- const danger = tool.name === "shell" ? classifyDanger(String(args.command ?? "")) : null;
194
+ const shellCommand = tool.name === "shell" ? String(args.command ?? "") : "";
195
+ const dangerLabel = tool.name === "shell" ? classifyDanger(shellCommand) : null;
196
+ // Separate from dangerLabel: this one only fires when no sandbox binary
197
+ // is installed, so a flagged command is about to run fully unconfined.
198
+ // Only surfaced when a human can actually see and answer the prompt —
199
+ // see `Agent.interactive`.
200
+ const sandboxWarning = tool.name === "shell" && dangerLabel === null && host.interactive
201
+ ? sandboxFallbackWarning(host.ctx.sandboxMode, shellCommand)
202
+ : null;
203
+ const danger = dangerLabel ?? sandboxWarning;
194
204
  const autoApproveEdit = host.acceptEdits &&
195
205
  danger === null &&
196
206
  tool.requiresPermission &&
@@ -214,6 +224,12 @@ export class ToolExecutor {
214
224
  // A forced (danger) prompt does not grant a lasting allowance.
215
225
  if (danger === null)
216
226
  host.permissions.record(tool.name, decision, args);
227
+ // Unlike a dangerLabel prompt (which re-warns every time on purpose),
228
+ // approving the unsandboxed-fallback warning once is enough — "auto"
229
+ // sandboxes nearly every command, so re-asking on each one would be
230
+ // an unusable wall of prompts for a fact that won't change mid-session.
231
+ if (decision === "yes" && sandboxWarning !== null)
232
+ acknowledgeUnsandboxedFallback();
217
233
  if (decision === "no") {
218
234
  host.audit?.logPermission({
219
235
  tool: name,
@@ -23,6 +23,11 @@ export const CURATED_MODELS = [
23
23
  label: "Nemotron 3 Ultra 550B",
24
24
  contextWindow: 128_000,
25
25
  },
26
+ {
27
+ id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
28
+ label: "Nemotron 3 Nano Omni 30B Reasoning",
29
+ contextWindow: 128_000,
30
+ },
26
31
  {
27
32
  id: "meta/muse-glimmer-30b",
28
33
  label: "Muse Glimmer 30B",
package/dist/headless.js CHANGED
@@ -164,6 +164,10 @@ export async function runHeadless(args) {
164
164
  // declined" for a question no one was actually asked.
165
165
  { workspace, sandboxMode: config.sandboxExec ?? defaultSandboxMode(), trustWorkspace }, permissions, session, initialHistory);
166
166
  agent.contextWindow = contextWindowFor(model, config);
167
+ // No one is present to answer a permission prompt, so a forced
168
+ // unsandboxed-fallback warning must resolve on its own (see
169
+ // requestPermission below) rather than be raised at all.
170
+ agent.interactive = false;
167
171
  if (config.maxSteps && config.maxSteps > 0)
168
172
  agent.maxSteps = config.maxSteps;
169
173
  if (config.toolTimeoutSeconds !== undefined) {
package/dist/index.js CHANGED
@@ -526,6 +526,10 @@ async function main() {
526
526
  // network access).
527
527
  const sub = new Agent(client, () => modelRef.current, writeSubTools, { workspace: wt.dir, sandboxMode, trustWorkspace }, new PermissionManager({ allow: ["write_file", "edit_file", "shell(*)"], deny: [] }, wt.dir), new SessionStore(wt.dir, true), []);
528
528
  sub.maxSteps = 30;
529
+ // No human is watching this run (see the auto-allow comment above) —
530
+ // the forced unsandboxed-fallback warning must resolve on its own via
531
+ // the handler below rather than be raised at all.
532
+ sub.interactive = false;
529
533
  sub.audit = sessionAudit;
530
534
  sub.tracer = sessionTracer;
531
535
  sub.meter = sessionMeter;
@@ -81,6 +81,45 @@ export function shouldSandbox(mode, command) {
81
81
  export function requiresSandbox(mode) {
82
82
  return mode === "strict";
83
83
  }
84
+ /**
85
+ * True once the user has explicitly approved running unsandboxed for the rest
86
+ * of this process — see `sandboxFallbackWarning`. Resets on restart; there is
87
+ * no persistent "don't ask again" for this, since a different host without
88
+ * the sandbox binary could be running next time.
89
+ */
90
+ let unsandboxedFallbackAcknowledged = false;
91
+ /** Records that the user approved the one-time unsandboxed-fallback warning. */
92
+ export function acknowledgeUnsandboxedFallback() {
93
+ unsandboxedFallbackAcknowledged = true;
94
+ }
95
+ /** Test-only: undoes `acknowledgeUnsandboxedFallback` between test cases. */
96
+ export function resetUnsandboxedFallbackAcknowledgement() {
97
+ unsandboxedFallbackAcknowledged = false;
98
+ }
99
+ /**
100
+ * A one-time, forced permission-prompt warning for the fail-open gap in
101
+ * "auto"/"always": sandboxing was requested for `command` but no sandbox
102
+ * binary is installed, so it's about to run completely unconfined — able to
103
+ * read, write, or delete anywhere the real user can, not just inside the
104
+ * workspace. Returns null (nothing to warn about) once
105
+ * `acknowledgeUnsandboxedFallback` has been called this run, when sandboxing
106
+ * wasn't requested for this command, when a sandbox binary IS available, or
107
+ * under "strict" (which refuses instead of falling back, so it has no silent
108
+ * gap to warn about).
109
+ */
110
+ export function sandboxFallbackWarning(mode, command) {
111
+ if (unsandboxedFallbackAcknowledged)
112
+ return null;
113
+ if (!shouldSandbox(mode, command))
114
+ return null;
115
+ if (requiresSandbox(mode))
116
+ return null;
117
+ if (sandboxAvailable())
118
+ return null;
119
+ return (`a command running with NO sandbox isolation (${sandboxUnavailableReason()}) — ` +
120
+ `it can read, write, or delete anywhere you can, not just inside the workspace. ` +
121
+ `Approving runs this command now and skips this warning for the rest of the session`);
122
+ }
84
123
  /**
85
124
  * Common tool-cache / global-install directories outside the workspace that
86
125
  * legitimate commands need to write to (package manager caches, toolchain
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kritya",
3
- "version": "0.8.4-beta",
3
+ "version": "0.8.6-beta",
4
4
  "description": "Kritya — a lean, provider-agnostic terminal coding agent (NVIDIA, OpenAI, OpenRouter, Groq, Ollama, and more)",
5
5
  "type": "module",
6
6
  "bin": {