@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 +5 -4
- package/dist/adapters/claude.js +55 -42
- package/dist/adapters/codex.js +38 -20
- package/dist/codex-app-server.js +8 -7
- package/dist/daemon.js +30 -0
- package/dist/main.js +40 -7
- package/dist/rpc.js +5 -0
- package/dist/session.js +3 -1
- package/dist/tui.js +34 -22
- package/package.json +1 -1
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 `
|
|
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
|
|
package/dist/adapters/claude.js
CHANGED
|
@@ -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
|
-
|
|
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}`;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/dist/codex-app-server.js
CHANGED
|
@@ -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
|
-
|
|
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} |
|
|
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
|
|
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",
|