@mandipadk7/kavi 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -9,8 +9,8 @@ Current capabilities:
9
9
  - `kavi init --no-commit`: skip the bootstrap commit and let `kavi open` or `kavi start` create the first base commit later.
10
10
  - `kavi doctor`: verify Node, Codex, Claude, git worktree support, and local readiness.
11
11
  - `kavi update`: check for and install a newer published Kavi package from npm.
12
- - `kavi start`: start a managed session without attaching the TUI.
13
- - `kavi open`: create a managed session with separate Codex and Claude worktrees and open the full-screen operator console, even from an empty folder or a repo with no `HEAD` yet.
12
+ - `kavi start`: start a managed session without attaching the TUI. Add `--approve-all` to run future Codex and Claude turns with full access and without Kavi approval prompts.
13
+ - `kavi open`: create a managed session with separate Codex and Claude worktrees and open the full-screen operator console, even from an empty folder or a repo with no `HEAD` yet. Add `--approve-all` to start in full-access mode.
14
14
  - `kavi resume`: reopen the operator console for the current repo session.
15
15
  - `kavi summary`: show a cohesive session-level view of progress, current changes, recent activity, and landing readiness.
16
16
  - `kavi result`: show the current or latest landed outcome as one cohesive result surface, including per-agent output and merged landing details.
@@ -52,12 +52,13 @@ Notes:
52
52
  - `kavi init` and `kavi open` now support the "empty folder to first managed session" path. If no git repo exists, Kavi initializes one; if git exists but no `HEAD` exists yet, Kavi creates the bootstrap commit it needs for worktrees.
53
53
  - Codex runs through `codex app-server` in managed mode, so Codex-side approvals now land in the same Kavi inbox as Claude hook approvals.
54
54
  - Claude still runs through the installed `claude` CLI with Kavi-managed hooks and approval decisions.
55
+ - `--approve-all` and the operator console's `!` toggle switch Kavi into a full-access mode for future turns. In that mode, Claude runs with `--dangerously-skip-permissions`, Codex runs with its no-approval danger-full-access equivalent, and Kavi stops prompting for approvals on those future turns.
55
56
  - `kavi doctor` now checks Claude auth readiness with `claude auth status`, and startup blocks if Claude is installed but not authenticated.
56
57
  - `kavi doctor` also validates ownership path rules for duplicates, repo escapes, and absolute-path mistakes before those rules affect routing.
57
58
  - `kavi doctor` now also flags overlapping cross-agent ownership rules that do not produce a clear specificity winner.
58
59
  - The dashboard and operator commands now use the daemon's local RPC socket instead of editing session files directly, and the TUI stays updated from pushed daemon snapshots rather than polling.
59
60
  - The socket is machine-local rather than repo-local to avoid Unix socket path-length issues in deep repos and temp directories.
60
- - The console is keyboard-driven: `1-9` switch views, `j/k` move selection, `[` and `]` cycle task detail sections, `,` and `.` cycle changed files, `{` and `}` cycle patch hunks, `A/C/Q/M` add review notes, `o/O` cycle existing threads, `T` reply, `E` edit, `R` resolve or reopen, `a` cycle thread assignee, `w` mark a thread as won't-fix, `x` mark it as accepted-risk, `F` queue a fix task, `H` queue a handoff task, `y/n` resolve approvals, and `c` opens the inline task composer with live route preview diagnostics.
61
+ - The console is keyboard-driven: `1-9` switch views, `j/k` move selection, `[` and `]` cycle task detail sections, `,` and `.` cycle changed files, `{` and `}` cycle patch hunks, `A/C/Q/M` add review notes, `o/O` cycle existing threads, `T` reply, `E` edit, `R` resolve or reopen, `a` cycle thread assignee, `w` mark a thread as won't-fix, `x` mark it as accepted-risk, `F` queue a fix task, `H` queue a handoff task, `y/n` resolve approvals, `!` toggles full-access mode, and `c` opens the inline task composer with live route preview diagnostics. Inside the composer, digits are treated as literal text and `Tab` cycles the route owner.
61
62
  - Review filters are available both in the CLI and the TUI: `kavi reviews --assignee operator --status open`, and inside the console use `u`, `v`, and `d` to cycle assignee, status, and disposition filters for the active diff context.
62
63
  - The claims inspector now shows active overlap hotspots and ownership-rule conflicts so routing pressure points are visible directly in the operator surface.
63
64
  - The claims and decision inspectors now also show recommendation-driven next actions, so hotspots, cross-agent review pressure, and ownership-config problems can be turned into follow-up tasks directly from the operator surface.
@@ -65,7 +66,7 @@ Notes:
65
66
  - Review threads now carry explicit assignees and richer dispositions, including `accepted risk` and `won't fix`, instead of relying only on free-form note text.
66
67
  - Successful follow-up tasks now auto-resolve linked open review threads, landed follow-up work marks those resolved threads as landed, and replying to a resolved thread reopens it.
67
68
  - `kavi update --check` reports newer published builds without installing them, and `kavi update` can apply a chosen `latest`, `beta`, or exact version after confirmation.
68
- - The operator console now includes a dedicated recommendations view. Use `8` to switch there, `Enter` to apply a recommendation, `P` to force an additional follow-up task when one is already open, `z` to dismiss, and `Z` to restore.
69
+ - The operator console now includes a dedicated recommendations view. Use `4` to switch there, `Enter` to apply a recommendation, `P` to force an additional follow-up task when one is already open, `z` to dismiss, and `Z` to restore.
69
70
  - `kavi land` now prints a clearer merged-result summary, including the pre-land change surface by agent, validation status, and how many review threads were landed as part of the merge.
70
71
  - After `kavi land`, run `kavi result` to inspect the persisted merged-result report and the latest per-agent outcome in one place.
71
72
 
@@ -135,9 +135,33 @@ export async function writeClaudeSettings(paths, session) {
135
135
  "--event",
136
136
  event
137
137
  ]);
138
+ const toolHooks = session.fullAccessMode ? {} : {
139
+ PreToolUse: [
140
+ {
141
+ matcher: "Bash|Edit|Write|MultiEdit",
142
+ hooks: [
143
+ {
144
+ type: "command",
145
+ command: buildHookCommand("PreToolUse")
146
+ }
147
+ ]
148
+ }
149
+ ],
150
+ PostToolUse: [
151
+ {
152
+ matcher: "Bash|Edit|Write|MultiEdit",
153
+ hooks: [
154
+ {
155
+ type: "command",
156
+ command: buildHookCommand("PostToolUse")
157
+ }
158
+ ]
159
+ }
160
+ ]
161
+ };
138
162
  const settings = {
139
163
  permissions: {
140
- defaultMode: "plan"
164
+ defaultMode: session.fullAccessMode ? "bypassPermissions" : "plan"
141
165
  },
142
166
  hooks: {
143
167
  SessionStart: [
@@ -151,28 +175,7 @@ export async function writeClaudeSettings(paths, session) {
151
175
  ]
152
176
  }
153
177
  ],
154
- PreToolUse: [
155
- {
156
- matcher: "Bash|Edit|Write|MultiEdit",
157
- hooks: [
158
- {
159
- type: "command",
160
- command: buildHookCommand("PreToolUse")
161
- }
162
- ]
163
- }
164
- ],
165
- PostToolUse: [
166
- {
167
- matcher: "Bash|Edit|Write|MultiEdit",
168
- hooks: [
169
- {
170
- type: "command",
171
- command: buildHookCommand("PostToolUse")
172
- }
173
- ]
174
- }
175
- ],
178
+ ...toolHooks,
176
179
  Notification: [
177
180
  {
178
181
  matcher: "permission_prompt|idle_prompt|auth_success",
@@ -205,6 +208,34 @@ export async function writeClaudeSettings(paths, session) {
205
208
  export function resolveClaudeSessionId(session) {
206
209
  return session.agentStatus.claude.sessionId ?? session.id;
207
210
  }
211
+ export function buildClaudeCommandArgs(session, claudeSessionId, prompt, settingsPath) {
212
+ const sessionArgs = session.agentStatus.claude.sessionId ? [
213
+ "--resume",
214
+ claudeSessionId
215
+ ] : [
216
+ "--session-id",
217
+ claudeSessionId
218
+ ];
219
+ return [
220
+ "-p",
221
+ "--output-format",
222
+ "json",
223
+ "--json-schema",
224
+ CLAUDE_ENVELOPE_SCHEMA,
225
+ "--settings",
226
+ settingsPath,
227
+ ...session.fullAccessMode ? [
228
+ "--permission-mode",
229
+ "bypassPermissions",
230
+ "--dangerously-skip-permissions"
231
+ ] : [
232
+ "--permission-mode",
233
+ "plan"
234
+ ],
235
+ ...sessionArgs,
236
+ prompt
237
+ ];
238
+ }
208
239
  export async function runClaudeTask(session, task, paths) {
209
240
  const worktree = findWorktree(session, "claude");
210
241
  const claudeSessionId = resolveClaudeSessionId(session);
@@ -215,25 +246,7 @@ export async function runClaudeTask(session, task, paths) {
215
246
  "",
216
247
  buildTaskPrompt(session, task, "claude")
217
248
  ].join("\n");
218
- const result = await runCommand(session.runtime.claudeExecutable, [
219
- "-p",
220
- "--output-format",
221
- "json",
222
- "--json-schema",
223
- CLAUDE_ENVELOPE_SCHEMA,
224
- "--settings",
225
- paths.claudeSettingsFile,
226
- "--permission-mode",
227
- "plan",
228
- ...session.agentStatus.claude.sessionId ? [
229
- "--resume",
230
- claudeSessionId
231
- ] : [
232
- "--session-id",
233
- claudeSessionId
234
- ],
235
- prompt
236
- ], {
249
+ const result = await runCommand(session.runtime.claudeExecutable, buildClaudeCommandArgs(session, claudeSessionId, prompt, paths.claudeSettingsFile), {
237
250
  cwd: worktree.path
238
251
  });
239
252
  const rawOutput = result.code === 0 ? result.stdout : `${result.stdout}\n${result.stderr}`;
@@ -128,19 +128,41 @@ function buildApprovalResponse(method, params, approved, remember) {
128
128
  throw new Error(`Unsupported Codex approval request: ${method}`);
129
129
  }
130
130
  }
131
- function buildThreadParams(session, worktree, developerInstructions) {
131
+ export function buildThreadParams(session, worktree, developerInstructions) {
132
132
  const configuredModel = session.config.agents.codex.model.trim();
133
133
  return {
134
134
  cwd: worktree.path,
135
- approvalPolicy: "on-request",
136
- approvalsReviewer: "user",
137
- sandbox: "workspace-write",
135
+ approvalPolicy: session.fullAccessMode ? "never" : "on-request",
136
+ sandbox: session.fullAccessMode ? "danger-full-access" : "workspace-write",
138
137
  baseInstructions: "You are Codex inside Kavi. Operate inside the assigned worktree and keep work task-scoped.",
139
138
  developerInstructions,
140
139
  model: configuredModel || null,
141
140
  ephemeral: false,
142
141
  experimentalRawEvents: false,
143
- persistExtendedHistory: true
142
+ persistExtendedHistory: true,
143
+ ...session.fullAccessMode ? {} : {
144
+ approvalsReviewer: "user"
145
+ }
146
+ };
147
+ }
148
+ export function buildCodexTurnParams(session, worktree, threadId, inputText) {
149
+ return {
150
+ threadId,
151
+ cwd: worktree.path,
152
+ approvalPolicy: session.fullAccessMode ? "never" : "on-request",
153
+ sandbox: session.fullAccessMode ? "danger-full-access" : "workspace-write",
154
+ model: session.config.agents.codex.model.trim() || null,
155
+ outputSchema: ENVELOPE_OUTPUT_SCHEMA,
156
+ input: [
157
+ {
158
+ type: "text",
159
+ text: inputText,
160
+ text_elements: []
161
+ }
162
+ ],
163
+ ...session.fullAccessMode ? {} : {
164
+ approvalsReviewer: "user"
165
+ }
144
166
  };
145
167
  }
146
168
  async function ensureThread(client, session, paths, worktree, developerInstructions) {
@@ -163,6 +185,16 @@ async function ensureThread(client, session, paths, worktree, developerInstructi
163
185
  }
164
186
  async function handleCodexApproval(session, paths, request) {
165
187
  const descriptor = describeCodexApprovalRequest(request.method, request.params);
188
+ if (session.fullAccessMode) {
189
+ await recordEvent(paths, session.id, "approval.full_access_bypassed", {
190
+ agent: "codex",
191
+ requestId: request.id,
192
+ method: request.method,
193
+ toolName: descriptor.toolName,
194
+ summary: descriptor.summary
195
+ });
196
+ return buildApprovalResponse(request.method, request.params, true, true);
197
+ }
166
198
  const rule = await findApprovalRule(paths, {
167
199
  repoRoot: session.repoRoot,
168
200
  agent: "codex",
@@ -218,21 +250,7 @@ export async function runCodexTask(session, task, paths) {
218
250
  try {
219
251
  await client.initialize();
220
252
  const threadId = await ensureThread(client, session, paths, worktree, developerInstructions);
221
- const result = await client.runTurn({
222
- threadId,
223
- cwd: worktree.path,
224
- approvalPolicy: "on-request",
225
- approvalsReviewer: "user",
226
- model: session.config.agents.codex.model.trim() || null,
227
- outputSchema: ENVELOPE_OUTPUT_SCHEMA,
228
- input: [
229
- {
230
- type: "text",
231
- text: buildTaskPrompt(session, task, "codex"),
232
- text_elements: []
233
- }
234
- ]
235
- });
253
+ const result = await client.runTurn(buildCodexTurnParams(session, worktree, threadId, buildTaskPrompt(session, task, "codex")));
236
254
  const rawOutput = `${result.assistantMessage}${result.stderr ? `\n\n[stderr]\n${result.stderr}` : ""}`;
237
255
  const envelope = extractJsonObject(result.assistantMessage);
238
256
  return {
@@ -1,4 +1,11 @@
1
1
  import { spawn } from "node:child_process";
2
+ export function buildCodexAppServerArgs() {
3
+ return [
4
+ "app-server",
5
+ "--listen",
6
+ "stdio://"
7
+ ];
8
+ }
2
9
  function asObject(value) {
3
10
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4
11
  }
@@ -29,13 +36,7 @@ export class CodexAppServerClient {
29
36
  closePromise = null;
30
37
  constructor(runtime, cwd, onRequest){
31
38
  this.onRequest = onRequest;
32
- this.child = spawn(runtime.codexExecutable, [
33
- "app-server",
34
- "--listen",
35
- "stdio://",
36
- "--session-source",
37
- "cli"
38
- ], {
39
+ this.child = spawn(runtime.codexExecutable, buildCodexAppServerArgs(), {
39
40
  cwd,
40
41
  stdio: [
41
42
  "pipe",
package/dist/daemon.js CHANGED
@@ -201,6 +201,13 @@ export class KaviDaemon {
201
201
  ok: true
202
202
  }
203
203
  };
204
+ case "setFullAccessMode":
205
+ await this.setFullAccessModeFromRpc(params);
206
+ return {
207
+ result: {
208
+ ok: true
209
+ }
210
+ };
204
211
  case "taskArtifact":
205
212
  return {
206
213
  result: await this.getTaskArtifactFromRpc(params)
@@ -445,6 +452,29 @@ export class KaviDaemon {
445
452
  await this.publishSnapshot("approval.resolved");
446
453
  });
447
454
  }
455
+ async setFullAccessModeFromRpc(params) {
456
+ await this.runMutation(async ()=>{
457
+ const enabled = params.enabled === true;
458
+ if (this.session.fullAccessMode === enabled) {
459
+ return;
460
+ }
461
+ this.session.fullAccessMode = enabled;
462
+ addDecisionRecord(this.session, {
463
+ kind: "approval",
464
+ agent: null,
465
+ summary: `${enabled ? "Enabled" : "Disabled"} approve-all mode`,
466
+ detail: enabled ? "Future Claude and Codex turns will run with full access and without Kavi approval prompts." : "Future Claude and Codex turns will return to standard approval and sandbox behavior.",
467
+ metadata: {
468
+ enabled
469
+ }
470
+ });
471
+ await saveSessionRecord(this.paths, this.session);
472
+ await recordEvent(this.paths, this.session.id, "session.full_access_mode_changed", {
473
+ enabled
474
+ });
475
+ await this.publishSnapshot("session.full_access_mode_changed");
476
+ });
477
+ }
448
478
  async getTaskArtifactFromRpc(params) {
449
479
  const taskId = typeof params.taskId === "string" ? params.taskId : "";
450
480
  if (!taskId) {
package/dist/main.js CHANGED
@@ -15,7 +15,7 @@ import { loadPackageInfo } from "./package-info.js";
15
15
  import { buildSessionId, resolveAppPaths } from "./paths.js";
16
16
  import { isProcessAlive, runCommand, runInteractiveCommand, spawnDetachedNode } from "./process.js";
17
17
  import { buildLandReport, loadLatestLandReport, saveLandReport } from "./reports.js";
18
- import { pingRpc, readSnapshot, rpcDismissRecommendation, rpcEnqueueTask, rpcNotifyExternalUpdate, rpcKickoff, rpcRecentEvents, rpcResolveApproval, rpcRestoreRecommendation, rpcShutdown, rpcTaskArtifact } from "./rpc.js";
18
+ import { pingRpc, readSnapshot, rpcDismissRecommendation, rpcEnqueueTask, rpcNotifyExternalUpdate, rpcKickoff, rpcRecentEvents, rpcResolveApproval, rpcSetFullAccessMode, rpcRestoreRecommendation, rpcShutdown, rpcTaskArtifact } from "./rpc.js";
19
19
  import { buildOperatorRecommendations, buildRecommendationActionPlan, dismissOperatorRecommendation, restoreOperatorRecommendation } from "./recommendations.js";
20
20
  import { findOwnershipRuleConflicts } from "./ownership.js";
21
21
  import { filterReviewNotes, markReviewNotesLandedForTasks } from "./reviews.js";
@@ -33,6 +33,9 @@ const CLAUDE_AUTO_ALLOW_TOOLS = new Set([
33
33
  "Grep",
34
34
  "LS"
35
35
  ]);
36
+ function hasFlag(args, name) {
37
+ return args.includes(name);
38
+ }
36
39
  function getFlag(args, name) {
37
40
  const index = args.indexOf(name);
38
41
  if (index === -1) {
@@ -112,8 +115,8 @@ function renderUsage() {
112
115
  " kavi init [--home] [--no-commit]",
113
116
  " kavi doctor [--json]",
114
117
  " kavi update [--check] [--dry-run] [--yes] [--tag latest|beta] [--version X.Y.Z]",
115
- " kavi start [--goal \"...\"]",
116
- " kavi open [--goal \"...\"]",
118
+ " kavi start [--goal \"...\"] [--approve-all]",
119
+ " kavi open [--goal \"...\"] [--approve-all]",
117
120
  " kavi resume",
118
121
  " kavi summary [--json]",
119
122
  " kavi result [--json]",
@@ -360,7 +363,10 @@ async function commandUpdate(cwd, args) {
360
363
  }
361
364
  console.log(`Updated ${packageInfo.name} from ${packageInfo.version} to ${targetVersion}.`);
362
365
  }
363
- async function startOrAttachSession(cwd, goal) {
366
+ function renderFullAccessWarning() {
367
+ return "WARNING: approve-all is enabled. Claude and Codex will run with full access, and Kavi approval prompts will be bypassed for future turns.";
368
+ }
369
+ async function startOrAttachSession(cwd, goal, enableFullAccessMode) {
364
370
  const prepared = await prepareProjectContext(cwd, {
365
371
  createRepository: true,
366
372
  ensureHeadCommit: false,
@@ -371,6 +377,11 @@ async function startOrAttachSession(cwd, goal) {
371
377
  try {
372
378
  const session = await loadSessionRecord(paths);
373
379
  if (isSessionLive(session) && await pingRpc(paths)) {
380
+ if (enableFullAccessMode && !session.fullAccessMode) {
381
+ await rpcSetFullAccessMode(paths, {
382
+ enabled: true
383
+ });
384
+ }
374
385
  if (goal) {
375
386
  await rpcKickoff(paths, goal);
376
387
  }
@@ -391,7 +402,7 @@ async function startOrAttachSession(cwd, goal) {
391
402
  const rpcEndpoint = paths.socketPath;
392
403
  await fs.writeFile(paths.commandsFile, "", "utf8");
393
404
  const worktrees = await ensureWorktrees(repoRoot, paths, sessionId, config, baseCommit);
394
- await createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint);
405
+ await createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint, enableFullAccessMode);
395
406
  if (prepared.createdRepository) {
396
407
  await recordEvent(paths, sessionId, "repo.initialized", {
397
408
  repoRoot
@@ -502,9 +513,13 @@ function buildRouteAnalytics(tasks) {
502
513
  }
503
514
  async function commandOpen(cwd, args) {
504
515
  const goal = getGoal(args);
505
- await startOrAttachSession(cwd, goal);
516
+ await startOrAttachSession(cwd, goal, hasFlag(args, "--approve-all"));
506
517
  const repoRoot = await detectRepoRoot(cwd);
507
518
  const paths = resolveAppPaths(repoRoot);
519
+ const session = await loadSessionRecord(paths);
520
+ if (session.fullAccessMode) {
521
+ console.log(renderFullAccessWarning());
522
+ }
508
523
  await attachTui(paths);
509
524
  }
510
525
  async function commandResume(cwd) {
@@ -514,14 +529,18 @@ async function commandResume(cwd) {
514
529
  }
515
530
  async function commandStart(cwd, args) {
516
531
  const goal = getGoal(args);
517
- const socketPath = await startOrAttachSession(cwd, goal);
532
+ const socketPath = await startOrAttachSession(cwd, goal, hasFlag(args, "--approve-all"));
518
533
  const repoRoot = await detectRepoRoot(cwd);
519
534
  const paths = resolveAppPaths(repoRoot);
520
535
  const session = await loadSessionRecord(paths);
521
536
  console.log(`Started Kavi session ${session.id}`);
522
537
  console.log(`Repo: ${repoRoot}`);
523
538
  console.log(`Control: ${socketPath}`);
539
+ console.log(`Access: ${session.fullAccessMode ? "approve-all" : "standard"}`);
524
540
  console.log(`Runtime: node=${session.runtime.nodeExecutable} codex=${session.runtime.codexExecutable} claude=${session.runtime.claudeExecutable}`);
541
+ if (session.fullAccessMode) {
542
+ console.log(renderFullAccessWarning());
543
+ }
525
544
  for (const worktree of session.worktrees){
526
545
  console.log(`- ${worktree.agent}: ${worktree.path}`);
527
546
  }
@@ -547,6 +566,7 @@ async function commandStatus(cwd, args) {
547
566
  goal: session.goal,
548
567
  daemonPid: session.daemonPid,
549
568
  daemonHeartbeatAt: session.daemonHeartbeatAt,
569
+ fullAccessMode: session.fullAccessMode,
550
570
  daemonHealthy: isSessionLive(session),
551
571
  rpcConnected: await pingRpc(paths),
552
572
  heartbeatAgeMs,
@@ -604,6 +624,7 @@ async function commandStatus(cwd, args) {
604
624
  console.log(`Status: ${payload.status}${payload.daemonHealthy ? " (healthy)" : " (stale or stopped)"}`);
605
625
  console.log(`Repo: ${payload.repoRoot}`);
606
626
  console.log(`Control: ${payload.socketPath}${payload.rpcConnected ? " (connected)" : " (disconnected)"}`);
627
+ console.log(`Access: ${payload.fullAccessMode ? "approve-all" : "standard"}`);
607
628
  console.log(`Goal: ${payload.goal ?? "-"}`);
608
629
  console.log(`Workflow stage: ${payload.workflowStage.label} | ${payload.workflowStage.detail}`);
609
630
  if (payload.latestLandReport) {
@@ -1528,6 +1549,18 @@ async function commandHook(args) {
1528
1549
  };
1529
1550
  if (session && agent === "claude" && eventName === "PreToolUse") {
1530
1551
  const descriptor = describeToolUse(payload);
1552
+ if (session.fullAccessMode) {
1553
+ console.log(JSON.stringify({
1554
+ continue: true,
1555
+ suppressOutput: true,
1556
+ hookSpecificOutput: {
1557
+ hookEventName: "PreToolUse",
1558
+ permissionDecision: "allow",
1559
+ permissionDecisionReason: `Kavi approve-all bypassed approval: ${descriptor.summary}`
1560
+ }
1561
+ }));
1562
+ return;
1563
+ }
1531
1564
  if (CLAUDE_AUTO_ALLOW_TOOLS.has(descriptor.toolName)) {
1532
1565
  await recordEvent(paths, session.id, "approval.auto_allowed", {
1533
1566
  agent,
package/dist/rpc.js CHANGED
@@ -124,6 +124,11 @@ export async function rpcRestoreRecommendation(paths, params) {
124
124
  export async function rpcResolveApproval(paths, params) {
125
125
  await sendRpcRequest(paths, "resolveApproval", params);
126
126
  }
127
+ export async function rpcSetFullAccessMode(paths, params) {
128
+ await sendRpcRequest(paths, "setFullAccessMode", {
129
+ enabled: params.enabled === true
130
+ });
131
+ }
127
132
  export async function rpcShutdown(paths) {
128
133
  await sendRpcRequest(paths, "shutdown");
129
134
  }
package/dist/session.js CHANGED
@@ -15,7 +15,7 @@ function initialAgentStatus(agent, transport) {
15
15
  summary: null
16
16
  };
17
17
  }
18
- export async function createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint) {
18
+ export async function createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint, fullAccessMode = false) {
19
19
  const timestamp = nowIso();
20
20
  const record = {
21
21
  id: sessionId,
@@ -26,6 +26,7 @@ export async function createSessionRecord(paths, config, runtime, sessionId, bas
26
26
  socketPath: rpcEndpoint,
27
27
  status: "starting",
28
28
  goal,
29
+ fullAccessMode,
29
30
  daemonPid: null,
30
31
  daemonHeartbeatAt: null,
31
32
  config,
@@ -50,6 +51,7 @@ export async function loadSessionRecord(paths) {
50
51
  if (!record.runtime) {
51
52
  record.runtime = await resolveSessionRuntime(paths);
52
53
  }
54
+ record.fullAccessMode = record.fullAccessMode === true;
53
55
  record.tasks = Array.isArray(record.tasks) ? record.tasks.map((task)=>({
54
56
  ...task,
55
57
  routeReason: typeof task.routeReason === "string" ? task.routeReason : null,
package/dist/tui.js CHANGED
@@ -6,7 +6,7 @@ import { findOwnershipRuleConflicts } from "./ownership.js";
6
6
  import { buildOperatorRecommendations, buildRecommendationActionPlan } from "./recommendations.js";
7
7
  import { cycleReviewAssignee, reviewNoteMatchesFilters } from "./reviews.js";
8
8
  import { extractPromptPathHints, previewRouteDecision, routeTask } from "./router.js";
9
- import { pingRpc, rpcAddReviewNote, rpcAddReviewReply, rpcDismissRecommendation, rpcEnqueueReviewFollowUp, readSnapshot, rpcEnqueueTask, rpcResolveApproval, rpcRestoreRecommendation, rpcSetReviewNoteStatus, rpcShutdown, rpcTaskArtifact, rpcUpdateReviewNote, rpcWorktreeDiff, subscribeSnapshotRpc } from "./rpc.js";
9
+ import { pingRpc, rpcAddReviewNote, rpcAddReviewReply, rpcDismissRecommendation, rpcEnqueueReviewFollowUp, readSnapshot, rpcEnqueueTask, rpcResolveApproval, rpcSetFullAccessMode, rpcRestoreRecommendation, rpcSetReviewNoteStatus, rpcShutdown, rpcTaskArtifact, rpcUpdateReviewNote, rpcWorktreeDiff, subscribeSnapshotRpc } from "./rpc.js";
10
10
  import { buildWorkflowActivity, buildWorkflowResult, buildWorkflowSummary } from "./workflow.js";
11
11
  const RESET = "\u001b[0m";
12
12
  const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
@@ -32,6 +32,15 @@ export const OPERATOR_TABS = [
32
32
  "messages",
33
33
  "worktrees"
34
34
  ];
35
+ export function nextComposerOwner(current, delta = 1) {
36
+ const order = [
37
+ "auto",
38
+ "codex",
39
+ "claude"
40
+ ];
41
+ const currentIndex = Math.max(0, order.indexOf(current));
42
+ return order[(currentIndex + delta + order.length) % order.length] ?? current;
43
+ }
35
44
  const TASK_DETAIL_SECTIONS = [
36
45
  "overview",
37
46
  "prompt",
@@ -1461,7 +1470,7 @@ function renderHeader(view, ui, width) {
1461
1470
  const workflowSummary = snapshot ? buildWorkflowSummary(snapshot) : null;
1462
1471
  const repoName = path.basename(session?.repoRoot ?? process.cwd());
1463
1472
  const line1 = fitAnsiLine(`${toneForPanel("Kavi Operator", true)} | session=${session?.id ?? "-"} | repo=${repoName} | rpc=${view.connected ? "connected" : "disconnected"}`, width);
1464
- const line2 = fitAnsiLine(`Goal: ${session?.goal ?? "-"} | stage=${workflowSummary?.stage.label ?? "-"} | status=${session?.status ?? "-"} | refresh=${shortTime(view.refreshedAt)}`, width);
1473
+ const line2 = fitAnsiLine(`Goal: ${session?.goal ?? "-"} | stage=${workflowSummary?.stage.label ?? "-"} | status=${session?.status ?? "-"} | access=${session?.fullAccessMode ? "approve-all" : "standard"} | refresh=${shortTime(view.refreshedAt)}`, width);
1465
1474
  const line3 = fitAnsiLine(session ? `Tasks P:${countTasks(session.tasks, "pending")} R:${countTasks(session.tasks, "running")} B:${countTasks(session.tasks, "blocked")} C:${countTasks(session.tasks, "completed")} F:${countTasks(session.tasks, "failed")} | approvals=${snapshot?.approvals.filter((approval)=>approval.status === "pending").length ?? 0} | reviews=${countOpenReviewNotes(snapshot)} | claims=${session.pathClaims.filter((claim)=>claim.status === "active").length} | recs=${buildOperatorRecommendations(session, {
1466
1475
  includeDismissed: true
1467
1476
  }).filter((item)=>item.status === "active").length}/${buildOperatorRecommendations(session, {
@@ -1513,7 +1522,7 @@ function renderFooter(snapshot, ui, width) {
1513
1522
  const previewMetadata = preview && preview.metadata && typeof preview.metadata === "object" ? preview.metadata : {};
1514
1523
  const winningRule = previewMetadata.winningRule && typeof previewMetadata.winningRule === "object" ? previewMetadata.winningRule : null;
1515
1524
  const composerHeader = fitAnsiLine(styleLine("Compose Task", "accent", "strong"), width);
1516
- const composerLine = fitAnsiLine(`Route: ${ui.composer.owner} | 1 auto 2 codex 3 claude | Enter submit | Esc cancel | Ctrl+U clear`, width);
1525
+ const composerLine = fitAnsiLine(`Route: ${ui.composer.owner} | Tab cycle route | Enter submit | Esc cancel | Ctrl+U clear`, width);
1517
1526
  const previewLine = fitAnsiLine(preview ? `Preview: ${preview.owner} via ${preview.strategy} (${preview.confidence.toFixed(2)}) | ${preview.reason}` : "Preview: waiting for prompt", width);
1518
1527
  const diagnosticsLine = fitAnsiLine(preview ? `Hints: ${preview.claimedPaths.join(", ") || "-"} | rule=${typeof winningRule?.pattern === "string" ? winningRule.pattern : "-"}` : "Hints: -", width);
1519
1528
  const promptLine = fitAnsiLine(`> ${ui.composer.prompt}`, width);
@@ -1523,14 +1532,14 @@ function renderFooter(snapshot, ui, width) {
1523
1532
  previewLine,
1524
1533
  diagnosticsLine,
1525
1534
  promptLine,
1526
- fitAnsiLine(toast ? styleLine(toast.message, toast.level === "error" ? "bad" : "good") : styleLine("Composer accepts free text; tasks are routed or assigned when you press Enter.", "muted"), width)
1535
+ fitAnsiLine(toast ? styleLine(toast.message, toast.level === "error" ? "bad" : "good") : styleLine(snapshot?.session.fullAccessMode ? "Approve-all is enabled. Future Claude and Codex turns will run with full access when you press Enter." : "Composer accepts free text; tasks are routed or assigned when you press Enter.", snapshot?.session.fullAccessMode ? "warn" : "muted"), width)
1527
1536
  ];
1528
1537
  }
1529
1538
  return [
1530
- fitAnsiLine("Keys: 1-9 tabs | h/l or Tab cycle tabs | j/k move | [ ] task detail | ,/. diff file | { } diff hunk | c compose | r refresh", width),
1539
+ fitAnsiLine("Keys: 1-9 tabs | h/l or Tab cycle tabs | j/k move | [ ] task detail | ,/. diff file | { } diff hunk | c compose | ! toggle approve-all | r refresh", width),
1531
1540
  fitAnsiLine("Actions: Enter apply rec | P force apply rec | z dismiss rec | Z restore rec | y/Y allow approval | n/N deny approval | A/C/Q/M add note | o/O select note | T reply | E edit | R resolve | a cycle assignee | u filter assignee | v filter status | d filter disposition | w won't fix | x accepted risk | F fix task | H handoff | g/G top/bottom | s stop daemon | q quit", width),
1532
1541
  footerSelectionSummary(snapshot, ui, width),
1533
- fitAnsiLine(toast ? styleLine(toast.message, toast.level === "error" ? "bad" : "good") : styleLine("Operator surface is live over the daemon socket with pushed snapshots.", "muted"), width)
1542
+ fitAnsiLine(toast ? styleLine(toast.message, toast.level === "error" ? "bad" : "good") : styleLine(snapshot?.session.fullAccessMode ? "Approve-all is enabled: Claude and Codex may run commands and edit files without Kavi approval prompts." : "Operator surface is live over the daemon socket with pushed snapshots.", snapshot?.session.fullAccessMode ? "warn" : "muted"), width)
1534
1543
  ];
1535
1544
  }
1536
1545
  function buildLayout(width, height) {
@@ -2101,6 +2110,18 @@ export async function attachTui(paths) {
2101
2110
  }
2102
2111
  });
2103
2112
  };
2113
+ const toggleFullAccessMode = async ()=>{
2114
+ const session = view.snapshot?.session ?? null;
2115
+ if (!session) {
2116
+ throw new Error("No session snapshot is loaded yet.");
2117
+ }
2118
+ const enabled = !session.fullAccessMode;
2119
+ await rpcSetFullAccessMode(paths, {
2120
+ enabled
2121
+ });
2122
+ setToast(ui, "info", enabled ? "Approve-all enabled. Future Claude and Codex turns will run with full access and without Kavi approval prompts." : "Approve-all disabled. Future Claude and Codex turns will return to standard approval and sandbox behavior.");
2123
+ render();
2124
+ };
2104
2125
  const close = ()=>{
2105
2126
  if (closed) {
2106
2127
  return;
@@ -2199,22 +2220,7 @@ export async function attachTui(paths) {
2199
2220
  return;
2200
2221
  }
2201
2222
  if (key.name === "tab" || input === "\t") {
2202
- ui.composer.owner = ui.composer.owner === "auto" ? "codex" : ui.composer.owner === "codex" ? "claude" : "auto";
2203
- render();
2204
- return;
2205
- }
2206
- if (input === "1") {
2207
- ui.composer.owner = "auto";
2208
- render();
2209
- return;
2210
- }
2211
- if (input === "2") {
2212
- ui.composer.owner = "codex";
2213
- render();
2214
- return;
2215
- }
2216
- if (input === "3") {
2217
- ui.composer.owner = "claude";
2223
+ ui.composer.owner = nextComposerOwner(ui.composer.owner, key.shift ? -1 : 1);
2218
2224
  render();
2219
2225
  return;
2220
2226
  }
@@ -2296,6 +2302,12 @@ export async function attachTui(paths) {
2296
2302
  runAction(refresh);
2297
2303
  return;
2298
2304
  }
2305
+ if (input === "!") {
2306
+ runAction(async ()=>{
2307
+ await toggleFullAccessMode();
2308
+ });
2309
+ return;
2310
+ }
2299
2311
  if (input === "c") {
2300
2312
  ui.composer = {
2301
2313
  owner: "auto",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandipadk7/kavi",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Managed Codex + Claude collaboration TUI",
5
5
  "type": "module",
6
6
  "preferGlobal": true,