klyro 0.1.62 → 1.0.0

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 (62) hide show
  1. package/dist/agent/anthropic-adapter.d.ts +30 -9
  2. package/dist/agent/anthropic-adapter.js +107 -49
  3. package/dist/agent/capabilities.d.ts +122 -0
  4. package/dist/agent/capabilities.js +150 -0
  5. package/dist/agent/orchestrator.d.ts +131 -0
  6. package/dist/agent/orchestrator.js +269 -0
  7. package/dist/agent/provider-adapter.d.ts +9 -0
  8. package/dist/agent/provider-adapter.js +85 -39
  9. package/dist/agent/registry.d.ts +1 -0
  10. package/dist/agent/registry.js +1 -0
  11. package/dist/agent/retry.d.ts +12 -1
  12. package/dist/agent/retry.js +19 -1
  13. package/dist/agent/runtime.d.ts +23 -1
  14. package/dist/agent/runtime.js +236 -26
  15. package/dist/agent/scoped-registry.d.ts +22 -0
  16. package/dist/agent/scoped-registry.js +42 -0
  17. package/dist/agent/task-manager.d.ts +115 -0
  18. package/dist/agent/task-manager.js +250 -0
  19. package/dist/agent/worker-spawner.d.ts +17 -12
  20. package/dist/agent/worker-spawner.js +26 -20
  21. package/dist/cli/config.d.ts +21 -0
  22. package/dist/cli/config.js +31 -0
  23. package/dist/cli/dotenv.d.ts +3 -0
  24. package/dist/cli/dotenv.js +57 -0
  25. package/dist/cli/repl.js +72 -4
  26. package/dist/cli/run.d.ts +3 -0
  27. package/dist/cli/run.js +127 -7
  28. package/dist/context/klyro-md.d.ts +6 -0
  29. package/dist/context/klyro-md.js +21 -15
  30. package/dist/context/trust.d.ts +42 -0
  31. package/dist/context/trust.js +111 -0
  32. package/dist/events/catalog.d.ts +71 -0
  33. package/dist/index.js +4 -0
  34. package/dist/mcp/client.d.ts +53 -0
  35. package/dist/mcp/client.js +225 -0
  36. package/dist/mcp/config.d.ts +30 -0
  37. package/dist/mcp/config.js +82 -0
  38. package/dist/mcp/policy.d.ts +13 -0
  39. package/dist/mcp/policy.js +12 -0
  40. package/dist/mcp/registry.d.ts +50 -0
  41. package/dist/mcp/registry.js +172 -0
  42. package/dist/mcp/schema.d.ts +11 -0
  43. package/dist/mcp/schema.js +46 -0
  44. package/dist/persistence/store.d.ts +1 -1
  45. package/dist/policy/approval.d.ts +27 -6
  46. package/dist/policy/approval.js +36 -8
  47. package/dist/policy/engine.d.ts +13 -0
  48. package/dist/policy/engine.js +31 -2
  49. package/dist/policy/patterns.d.ts +16 -0
  50. package/dist/policy/patterns.js +26 -0
  51. package/dist/tools/agent/spawn-agent.d.ts +9 -0
  52. package/dist/tools/agent/spawn-agent.js +50 -0
  53. package/dist/tools/agent/task-get.d.ts +8 -0
  54. package/dist/tools/agent/task-get.js +40 -0
  55. package/dist/tools/agent/task-list.d.ts +4 -0
  56. package/dist/tools/agent/task-list.js +41 -0
  57. package/dist/tools/plan/todo-write.d.ts +1 -1
  58. package/dist/tools/registry.js +6 -0
  59. package/dist/tools/types.d.ts +12 -0
  60. package/dist/tui/approval.d.ts +1 -1
  61. package/dist/tui/approval.js +2 -2
  62. package/package.json +2 -2
@@ -5,12 +5,22 @@
5
5
  * Non-TTY: returns deny. Callers should treat non-TTY as the default
6
6
  * (the user opted out of prompts with `--yes` or pipe input).
7
7
  */
8
- export type ApprovalChoice = 'allow' | 'deny' | 'always';
8
+ export type ApprovalChoice =
9
+ /** Yes, just this once. */
10
+ 'allow' | 'deny'
11
+ /** Yes, and auto-allow this pattern for the rest of the session. */
12
+ | 'always'
13
+ /** Yes, session-allow AND persist the pattern to settings (survives restarts). */
14
+ | 'always-persist';
9
15
  export interface ApprovalRequest {
10
16
  toolName: string;
11
17
  reason: string;
12
18
  /** Best-effort summary of the call (command or path). */
13
19
  summary: string;
20
+ /** Raw tool input — used to derive the approval pattern when `pattern` is absent. */
21
+ input?: Record<string, unknown>;
22
+ /** Pre-derived approval pattern (`tool(glob)` grammar). */
23
+ pattern?: string;
14
24
  }
15
25
  export interface ApprovalPrompt {
16
26
  ask(req: ApprovalRequest): Promise<ApprovalChoice>;
@@ -24,12 +34,23 @@ export declare class DenyAllApprovalPrompt implements ApprovalPrompt {
24
34
  ask(_req: ApprovalRequest): Promise<ApprovalChoice>;
25
35
  }
26
36
  /**
27
- * In-memory allowlist: tracks "always" choices by command prefix so a
28
- * single prompt per command covers repeat invocations in the same session.
37
+ * Pattern approval cache — ask-once-per-pattern (Claude-Code-like).
38
+ *
39
+ * Wraps any inner prompt (TUI modal, stdin). The first `ask` for a pattern
40
+ * delegates to the user; `always` records the pattern for the session,
41
+ * `always-persist` additionally calls `onPersist` (the CLI wires this to
42
+ * append the pattern to ~/.klyro/settings.json and the live engine).
43
+ * Re-prompts never happen for a recorded pattern within the session.
44
+ *
45
+ * The pattern comes from `req.pattern` (pre-derived by the runtime via
46
+ * `patternForCall`) or is derived from `req.input` here as a fallback.
29
47
  */
30
- export declare class InMemoryAllowlist implements ApprovalPrompt {
48
+ export declare class PatternApprovalCache implements ApprovalPrompt {
31
49
  private readonly inner;
32
- private readonly allow;
33
- constructor(inner?: ApprovalPrompt);
50
+ private readonly opts;
51
+ private readonly session;
52
+ constructor(inner?: ApprovalPrompt, opts?: {
53
+ onPersist?: (pattern: string) => void | Promise<void>;
54
+ });
34
55
  ask(req: ApprovalRequest): Promise<ApprovalChoice>;
35
56
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import * as readline from 'node:readline/promises';
9
9
  import { stdin as input, stdout as output } from 'node:process';
10
+ import { patternForCall } from './patterns.js';
10
11
  /** Default prompt backed by readline on stdin/stdout. */
11
12
  export class StdinApprovalPrompt {
12
13
  async ask(req) {
@@ -34,21 +35,48 @@ export class DenyAllApprovalPrompt {
34
35
  }
35
36
  }
36
37
  /**
37
- * In-memory allowlist: tracks "always" choices by command prefix so a
38
- * single prompt per command covers repeat invocations in the same session.
38
+ * Pattern approval cache — ask-once-per-pattern (Claude-Code-like).
39
+ *
40
+ * Wraps any inner prompt (TUI modal, stdin). The first `ask` for a pattern
41
+ * delegates to the user; `always` records the pattern for the session,
42
+ * `always-persist` additionally calls `onPersist` (the CLI wires this to
43
+ * append the pattern to ~/.klyro/settings.json and the live engine).
44
+ * Re-prompts never happen for a recorded pattern within the session.
45
+ *
46
+ * The pattern comes from `req.pattern` (pre-derived by the runtime via
47
+ * `patternForCall`) or is derived from `req.input` here as a fallback.
39
48
  */
40
- export class InMemoryAllowlist {
49
+ export class PatternApprovalCache {
41
50
  inner;
42
- allow = new Set();
43
- constructor(inner = new DenyAllApprovalPrompt()) {
51
+ opts;
52
+ session = new Set();
53
+ constructor(inner = new DenyAllApprovalPrompt(), opts = {}) {
44
54
  this.inner = inner;
55
+ this.opts = opts;
45
56
  }
46
57
  async ask(req) {
47
- if (this.allow.has(req.summary))
58
+ const key = req.pattern ?? patternFromRequest(req);
59
+ if (key && this.session.has(key))
48
60
  return 'allow';
49
61
  const choice = await this.inner.ask(req);
50
- if (choice === 'always')
51
- this.allow.add(req.summary);
62
+ if ((choice === 'always' || choice === 'always-persist') && key) {
63
+ this.session.add(key);
64
+ }
65
+ if (choice === 'always-persist' && key) {
66
+ // Best-effort: the current call is already approved via the session
67
+ // set above — a persist failure must not retro-deny it.
68
+ try {
69
+ await this.opts.onPersist?.(key);
70
+ }
71
+ catch {
72
+ /* ignore */
73
+ }
74
+ }
52
75
  return choice;
53
76
  }
54
77
  }
78
+ function patternFromRequest(req) {
79
+ if (!req.input)
80
+ return undefined;
81
+ return patternForCall(req.toolName, req.input);
82
+ }
@@ -67,6 +67,19 @@ export declare class PolicyEngine {
67
67
  nonInteractive: boolean;
68
68
  }): Promise<Decision>;
69
69
  private evaluateGlobRules;
70
+ /**
71
+ * Session rule management — appends a glob rule (`tool(glob)` grammar)
72
+ * if not already present. Used for persisted "always allow" patterns
73
+ * (loaded at startup) and live additions (approval `always→settings`).
74
+ */
75
+ addAllow(rule: string): void;
76
+ addDeny(rule: string): void;
77
+ addAsk(rule: string): void;
78
+ applyRules(rules: {
79
+ allow?: string[];
80
+ deny?: string[];
81
+ ask?: string[];
82
+ }): void;
70
83
  }
71
84
  /** Builtin set of rules. Order matters: first match wins. */
72
85
  export declare function builtinRules(): PolicyRule[];
@@ -116,6 +116,34 @@ export class PolicyEngine {
116
116
  // Precedence: deny → allow → ask
117
117
  return check(this.config.deny, 'deny') ?? check(this.config.allow, 'allow') ?? check(this.config.ask, 'ask') ?? null;
118
118
  }
119
+ /**
120
+ * Session rule management — appends a glob rule (`tool(glob)` grammar)
121
+ * if not already present. Used for persisted "always allow" patterns
122
+ * (loaded at startup) and live additions (approval `always→settings`).
123
+ */
124
+ addAllow(rule) {
125
+ const list = (this.config.allow ??= []);
126
+ if (!list.includes(rule))
127
+ list.push(rule);
128
+ }
129
+ addDeny(rule) {
130
+ const list = (this.config.deny ??= []);
131
+ if (!list.includes(rule))
132
+ list.push(rule);
133
+ }
134
+ addAsk(rule) {
135
+ const list = (this.config.ask ??= []);
136
+ if (!list.includes(rule))
137
+ list.push(rule);
138
+ }
139
+ applyRules(rules) {
140
+ for (const r of rules.allow ?? [])
141
+ this.addAllow(r);
142
+ for (const r of rules.deny ?? [])
143
+ this.addDeny(r);
144
+ for (const r of rules.ask ?? [])
145
+ this.addAsk(r);
146
+ }
119
147
  }
120
148
  /** Builtin set of rules. Order matters: first match wins. */
121
149
  export function builtinRules() {
@@ -130,8 +158,9 @@ function asString(v) {
130
158
  return typeof v === 'string' ? v : '';
131
159
  }
132
160
  function matchesGlobRule(call, rule) {
133
- // Rule grammar: tool or tool(glob). e.g. "write_file", "write_file(.env)", "shell_exec(npm *)"
134
- const m = /^([a-z_]+)(?:\((.*)\))?$/.exec(rule.trim());
161
+ // Rule grammar: tool or tool(glob). e.g. "write_file", "write_file(.env)", "shell_exec(npm *)".
162
+ // Digits are allowed so generated names (e.g. mcp__server__tool2) can be targeted.
163
+ const m = /^([a-z0-9_]+)(?:\((.*)\))?$/.exec(rule.trim());
135
164
  if (!m)
136
165
  return false;
137
166
  const tool = m[1];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Approval patterns — the unit of "always allow this pattern".
3
+ *
4
+ * A pattern uses the same `tool(glob)` grammar the policy engine matches
5
+ * (`matchesGlobRule` in engine.ts), e.g. `shell_exec(npm *)`,
6
+ * `write_file(src/index.ts)`. Derivation is deterministic: the same call
7
+ * shape always yields the same pattern, so session caching can key on exact
8
+ * pattern equality and persisted patterns re-match across sessions.
9
+ *
10
+ * Scoping (Claude-Code-like):
11
+ * - shell_exec / run_verify → first command word + ` *`
12
+ * (`npm test` and `npm run build` share `shell_exec(npm *)`)
13
+ * - file tools with a path → the exact path (safest: no wildcards)
14
+ * - everything else → the bare tool name (whole-tool session allow)
15
+ */
16
+ export declare function patternForCall(name: string, input: Record<string, unknown>): string;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Approval patterns — the unit of "always allow this pattern".
3
+ *
4
+ * A pattern uses the same `tool(glob)` grammar the policy engine matches
5
+ * (`matchesGlobRule` in engine.ts), e.g. `shell_exec(npm *)`,
6
+ * `write_file(src/index.ts)`. Derivation is deterministic: the same call
7
+ * shape always yields the same pattern, so session caching can key on exact
8
+ * pattern equality and persisted patterns re-match across sessions.
9
+ *
10
+ * Scoping (Claude-Code-like):
11
+ * - shell_exec / run_verify → first command word + ` *`
12
+ * (`npm test` and `npm run build` share `shell_exec(npm *)`)
13
+ * - file tools with a path → the exact path (safest: no wildcards)
14
+ * - everything else → the bare tool name (whole-tool session allow)
15
+ */
16
+ export function patternForCall(name, input) {
17
+ if ((name === 'shell_exec' || name === 'run_verify') && typeof input.command === 'string') {
18
+ const first = input.command.trim().split(/\s+/, 1)[0] ?? '';
19
+ if (first)
20
+ return `${name}(${first} *)`;
21
+ }
22
+ if (typeof input.path === 'string' && input.path.length > 0) {
23
+ return `${name}(${input.path})`;
24
+ }
25
+ return name;
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { ChildSummary } from '../../agent/orchestrator.js';
2
+ export declare const spawnAgentTool: import("../types.js").Tool<{
3
+ agent: string;
4
+ task: string;
5
+ cwd?: string | undefined;
6
+ model?: string | undefined;
7
+ timeoutMs?: number | undefined;
8
+ }, ChildSummary>;
9
+ export type SpawnAgentOutput = ChildSummary;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * P1.1 — spawn_agent tool (6-10fix.md §5).
3
+ *
4
+ * Delegates to `AgentSpawnBridge.spawnAgent`, which runs the child to
5
+ * completion (P0/P1 scope: blocking) and returns a compact ChildSummary —
6
+ * never the child transcript. True background `task_wait` / `task_stop`
7
+ * arrives in P2.
8
+ */
9
+ import { z } from 'zod';
10
+ import { defineTool } from '../types.js';
11
+ import { toToolError } from '../normalize.js';
12
+ const InputSchema = z.object({
13
+ agent: z.string().min(1).describe('Agent id (e.g. explorer, implementer, tester, reviewer)'),
14
+ task: z.string().min(1).describe('Task brief for the child agent'),
15
+ cwd: z.string().optional().describe('Working directory override (defaults to parent cwd)'),
16
+ model: z.string().optional().describe('Model override for the child (must be honoured or error)'),
17
+ timeoutMs: z.number().int().positive().optional().describe('Child runtime timeout in ms'),
18
+ });
19
+ export const spawnAgentTool = defineTool({
20
+ name: 'spawn_agent',
21
+ description: 'Spawn a child agent and return its compact summary { taskId, status, ... }. Child transcript stays separate; use task_get for status.',
22
+ inputSchema: InputSchema,
23
+ permission: 'admin',
24
+ isConcurrencySafe: false,
25
+ renderCall: (input) => `spawn_agent(${input.agent}): ${input.task.slice(0, 80)}`,
26
+ execute: async (input, ctx) => {
27
+ try {
28
+ const bridge = ctx.agentBridge;
29
+ if (!bridge) {
30
+ return {
31
+ ok: false,
32
+ error: {
33
+ code: 'NO_ORCHESTRATOR',
34
+ message: 'spawn_agent requires an active orchestrator (run with --agent or via a parent agent)',
35
+ },
36
+ };
37
+ }
38
+ return await bridge.spawnAgent({
39
+ agent: input.agent,
40
+ task: input.task,
41
+ ...(input.cwd ? { cwd: input.cwd } : {}),
42
+ ...(input.model ? { model: input.model } : {}),
43
+ ...(input.timeoutMs ? { timeoutMs: input.timeoutMs } : {}),
44
+ });
45
+ }
46
+ catch (err) {
47
+ return { ok: false, error: toToolError(err) };
48
+ }
49
+ },
50
+ });
@@ -0,0 +1,8 @@
1
+ export declare const taskGetTool: import("../types.js").Tool<{
2
+ taskId: string;
3
+ }, import("../../agent/task-manager.js").TaskSummary & {
4
+ error?: {
5
+ code: string;
6
+ message: string;
7
+ };
8
+ }>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * P1.3 — task_get tool (6-10fix.md §5). Read-only + concurrency-safe.
3
+ * Returns one task's status and summary by id.
4
+ */
5
+ import { z } from 'zod';
6
+ import { defineTool } from '../types.js';
7
+ import { toToolError } from '../normalize.js';
8
+ const InputSchema = z.object({
9
+ taskId: z.string().min(1).describe('Task id (e.g. task_1)'),
10
+ });
11
+ export const taskGetTool = defineTool({
12
+ name: 'task_get',
13
+ description: "Return one child-agent task's status and summary by id.",
14
+ inputSchema: InputSchema,
15
+ permission: 'read',
16
+ isConcurrencySafe: true,
17
+ renderCall: (input) => `task_get(${input.taskId})`,
18
+ execute: async (input, ctx) => {
19
+ try {
20
+ const bridge = ctx.agentBridge;
21
+ if (!bridge) {
22
+ return {
23
+ ok: false,
24
+ error: { code: 'NO_ORCHESTRATOR', message: 'task_get requires an active orchestrator' },
25
+ };
26
+ }
27
+ const rec = bridge.getTask(input.taskId);
28
+ if (!rec) {
29
+ return {
30
+ ok: false,
31
+ error: { code: 'NOT_FOUND', message: `Unknown task: ${input.taskId}` },
32
+ };
33
+ }
34
+ return { ok: true, value: rec };
35
+ }
36
+ catch (err) {
37
+ return { ok: false, error: toToolError(err) };
38
+ }
39
+ },
40
+ });
@@ -0,0 +1,4 @@
1
+ export declare const taskListTool: import("../types.js").Tool<{
2
+ parentTaskId?: string | undefined;
3
+ status?: "running" | "succeeded" | "failed" | "cancelled" | "timed_out" | "blocked" | "queued" | undefined;
4
+ }, import("../../agent/task-manager.js").TaskSummary[]>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * P1.2 — task_list tool (6-10fix.md §5). Read-only + concurrency-safe.
3
+ * Returns TaskSummary[] snapshots from the orchestrator's TaskManager.
4
+ */
5
+ import { z } from 'zod';
6
+ import { defineTool } from '../types.js';
7
+ import { toToolError } from '../normalize.js';
8
+ const InputSchema = z.object({
9
+ parentTaskId: z.string().optional().describe('Filter by parent task id'),
10
+ status: z
11
+ .enum(['queued', 'running', 'succeeded', 'failed', 'cancelled', 'timed_out', 'blocked'])
12
+ .optional()
13
+ .describe('Filter by status'),
14
+ });
15
+ export const taskListTool = defineTool({
16
+ name: 'task_list',
17
+ description: 'List child-agent tasks (running and completed) with status summaries.',
18
+ inputSchema: InputSchema,
19
+ permission: 'read',
20
+ isConcurrencySafe: true,
21
+ renderCall: () => 'task_list',
22
+ execute: async (input, ctx) => {
23
+ try {
24
+ const bridge = ctx.agentBridge;
25
+ if (!bridge) {
26
+ return {
27
+ ok: false,
28
+ error: { code: 'NO_ORCHESTRATOR', message: 'task_list requires an active orchestrator' },
29
+ };
30
+ }
31
+ const tasks = bridge.listTasks({
32
+ ...(input.parentTaskId ? { parentTaskId: input.parentTaskId } : {}),
33
+ ...(input.status ? { status: input.status } : {}),
34
+ });
35
+ return { ok: true, value: tasks };
36
+ }
37
+ catch (err) {
38
+ return { ok: false, error: toToolError(err) };
39
+ }
40
+ },
41
+ });
@@ -2,7 +2,7 @@ export declare const todoWriteTool: import("../types.js").Tool<{
2
2
  todos: {
3
3
  id: string;
4
4
  title: string;
5
- status: "done" | "pending" | "in_progress" | "failed" | "skipped";
5
+ status: "done" | "pending" | "failed" | "in_progress" | "skipped";
6
6
  files?: string[] | undefined;
7
7
  }[];
8
8
  }, {
@@ -25,6 +25,9 @@ import { findSymbolTool } from './symbols/find-symbol.js';
25
25
  import { lspDiagnosticsTool, lspGotoDefinitionTool } from './lsp/diagnostics.js';
26
26
  import { expandResultTool } from './expand-result.js';
27
27
  import { memoryWriteTool } from './memory-write.js';
28
+ import { spawnAgentTool } from './agent/spawn-agent.js';
29
+ import { taskListTool } from './agent/task-list.js';
30
+ import { taskGetTool } from './agent/task-get.js';
28
31
  import { zodToJsonSchema } from './schema.js';
29
32
  export class ToolRegistry {
30
33
  tools = new Map();
@@ -107,5 +110,8 @@ export const builtinRegistry = () => {
107
110
  r.register(lspGotoDefinitionTool);
108
111
  r.register(expandResultTool);
109
112
  r.register(memoryWriteTool);
113
+ r.register(spawnAgentTool);
114
+ r.register(taskListTool);
115
+ r.register(taskGetTool);
110
116
  return r;
111
117
  };
@@ -48,6 +48,18 @@ export interface ToolContext {
48
48
  };
49
49
  /** Emit KlyroEvent */
50
50
  emit?: (ev: import('../events/catalog.js').KlyroEvent) => void;
51
+ /** Delegation bridge (set when an orchestrator is active). Powers spawn_agent/task_* tools. */
52
+ agentBridge?: import('../agent/orchestrator.js').AgentSpawnBridge;
53
+ /** The task id this agent belongs to, when running under an orchestrator. */
54
+ parentTaskId?: string;
55
+ /** Depth of the current agent in the spawn tree (0 = root). Used to build ParentContextRef for spawn_agent. */
56
+ agentDepth?: number;
57
+ /** Max allowed spawn depth for this branch. */
58
+ agentMaxDepth?: number;
59
+ /** Effective tool allow-set of the current agent (null = all). Child can never exceed this. */
60
+ agentAllowedTools?: ReadonlySet<string> | null;
61
+ /** Model override active for the current agent. */
62
+ agentModel?: string;
51
63
  }
52
64
  /**
53
65
  * A typed tool definition. TInput is the validated input shape; TOutput is
@@ -13,7 +13,7 @@
13
13
  * a Promise.
14
14
  * 3. The App polls getPending() on every render to show a modal.
15
15
  * 4. The App's useInput handler sees a pending prompt and routes
16
- * y/n/a to the resolver.
16
+ * y/a/A/n to the resolver (A = always-persist to settings).
17
17
  *
18
18
  * Non-TTY mode (no Ink mounted) keeps using StdinApprovalPrompt; this
19
19
  * module is only used when the TUI is active.
@@ -14,7 +14,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
14
14
  * a Promise.
15
15
  * 3. The App polls getPending() on every render to show a modal.
16
16
  * 4. The App's useInput handler sees a pending prompt and routes
17
- * y/n/a to the resolver.
17
+ * y/a/A/n to the resolver (A = always-persist to settings).
18
18
  *
19
19
  * Non-TTY mode (no Ink mounted) keeps using StdinApprovalPrompt; this
20
20
  * module is only used when the TUI is active.
@@ -85,7 +85,7 @@ export function ApprovalModal({ bridge }) {
85
85
  return;
86
86
  }
87
87
  if (c === 'A') {
88
- bridge.resolve('always');
88
+ bridge.resolve('always-persist');
89
89
  return;
90
90
  }
91
91
  if (c === 'd' || c === 'n') {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.62",
4
- "description": "Klyro \u2014 autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
3
+ "version": "1.0.0",
4
+ "description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",