@mandipadk7/kavi 0.1.0 → 0.1.2

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/dist/main.js CHANGED
@@ -6,13 +6,15 @@ import { createApprovalRequest, describeToolUse, findApprovalRule, listApprovalR
6
6
  import { appendCommand } from "./command-queue.js";
7
7
  import { ensureHomeConfig, ensureProjectScaffold, loadConfig } from "./config.js";
8
8
  import { KaviDaemon } from "./daemon.js";
9
+ import { addDecisionRecord, upsertPathClaim } from "./decision-ledger.js";
9
10
  import { runDoctor } from "./doctor.js";
10
11
  import { writeJson } from "./fs.js";
11
- import { createGitignoreEntries, detectRepoRoot, ensureWorktrees, getHeadCommit, landBranches, resolveTargetBranch } from "./git.js";
12
+ import { createGitignoreEntries, detectRepoRoot, ensureWorktrees, findOverlappingWorktreePaths, getHeadCommit, landBranches, resolveTargetBranch } from "./git.js";
12
13
  import { buildSessionId, resolveAppPaths } from "./paths.js";
13
14
  import { isProcessAlive, spawnDetachedNode } from "./process.js";
15
+ import { pingRpc, readSnapshot, rpcEnqueueTask, rpcNotifyExternalUpdate, rpcKickoff, rpcRecentEvents, rpcResolveApproval, rpcShutdown, rpcTaskArtifact } from "./rpc.js";
14
16
  import { resolveSessionRuntime } from "./runtime.js";
15
- import { routePrompt } from "./router.js";
17
+ import { buildAdHocTask, extractPromptPathHints, routeTask } from "./router.js";
16
18
  import { createSessionRecord, loadSessionRecord, readRecentEvents, recordEvent, sessionExists, sessionHeartbeatAgeMs } from "./session.js";
17
19
  import { listTaskArtifacts, loadTaskArtifact } from "./task-artifacts.js";
18
20
  import { attachTui } from "./tui.js";
@@ -67,6 +69,8 @@ function renderUsage() {
67
69
  " kavi task [--agent codex|claude|auto] <prompt>",
68
70
  " kavi tasks [--json]",
69
71
  " kavi task-output <task-id|latest> [--json]",
72
+ " kavi decisions [--json] [--limit N]",
73
+ " kavi claims [--json] [--all]",
70
74
  " kavi approvals [--json] [--all]",
71
75
  " kavi approve <request-id|latest> [--remember]",
72
76
  " kavi deny <request-id|latest> [--remember]",
@@ -95,7 +99,7 @@ async function waitForSession(paths, expectedState = "running") {
95
99
  try {
96
100
  if (await sessionExists(paths)) {
97
101
  const session = await loadSessionRecord(paths);
98
- if (expectedState === "running" && isSessionLive(session)) {
102
+ if (expectedState === "running" && isSessionLive(session) && await pingRpc(paths)) {
99
103
  return;
100
104
  }
101
105
  if (expectedState === "stopped" && session.status === "stopped") {
@@ -143,14 +147,13 @@ async function startOrAttachSession(cwd, goal) {
143
147
  await ensureProjectScaffold(paths);
144
148
  await createGitignoreEntries(repoRoot);
145
149
  await ensureHomeConfig(paths);
150
+ await ensureStartupReady(repoRoot, paths);
146
151
  if (await sessionExists(paths)) {
147
152
  try {
148
153
  const session = await loadSessionRecord(paths);
149
- if (isSessionLive(session)) {
154
+ if (isSessionLive(session) && await pingRpc(paths)) {
150
155
  if (goal) {
151
- await appendCommand(paths, "kickoff", {
152
- prompt: goal
153
- });
156
+ await rpcKickoff(paths, goal);
154
157
  }
155
158
  return session.socketPath;
156
159
  }
@@ -164,7 +167,7 @@ async function startOrAttachSession(cwd, goal) {
164
167
  const runtime = await resolveSessionRuntime(paths);
165
168
  const baseCommit = await getHeadCommit(repoRoot);
166
169
  const sessionId = buildSessionId();
167
- const rpcEndpoint = "file://session-state";
170
+ const rpcEndpoint = paths.socketPath;
168
171
  await fs.writeFile(paths.commandsFile, "", "utf8");
169
172
  const worktrees = await ensureWorktrees(repoRoot, paths, sessionId, config, baseCommit);
170
173
  await createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint);
@@ -180,6 +183,23 @@ async function startOrAttachSession(cwd, goal) {
180
183
  await waitForSession(paths);
181
184
  return rpcEndpoint;
182
185
  }
186
+ async function ensureStartupReady(repoRoot, paths) {
187
+ const checks = await runDoctor(repoRoot, paths);
188
+ const required = new Set([
189
+ "node",
190
+ "codex",
191
+ "claude",
192
+ "git-worktree",
193
+ "codex-app-server",
194
+ "codex-auth-file"
195
+ ]);
196
+ const failures = checks.filter((check)=>required.has(check.name) && !check.ok);
197
+ if (failures.length === 0) {
198
+ return;
199
+ }
200
+ const details = failures.map((check)=>`${check.name}: ${check.detail}`).join("\n");
201
+ throw new Error(`Kavi startup blocked by failing readiness checks.\n${details}\nRun "kavi doctor" for the full report.`);
202
+ }
183
203
  async function requireSession(cwd) {
184
204
  const repoRoot = await detectRepoRoot(cwd);
185
205
  const paths = resolveAppPaths(repoRoot);
@@ -191,6 +211,20 @@ async function requireSession(cwd) {
191
211
  paths
192
212
  };
193
213
  }
214
+ async function tryRpcSnapshot(paths) {
215
+ if (!await pingRpc(paths)) {
216
+ return null;
217
+ }
218
+ return await readSnapshot(paths);
219
+ }
220
+ async function notifyOperatorSurface(paths, reason) {
221
+ if (!await pingRpc(paths)) {
222
+ return;
223
+ }
224
+ try {
225
+ await rpcNotifyExternalUpdate(paths, reason);
226
+ } catch {}
227
+ }
194
228
  async function commandOpen(cwd, args) {
195
229
  const goal = getGoal(args);
196
230
  await startOrAttachSession(cwd, goal);
@@ -219,17 +253,20 @@ async function commandStart(cwd, args) {
219
253
  }
220
254
  async function commandStatus(cwd, args) {
221
255
  const { paths } = await requireSession(cwd);
222
- const session = await loadSessionRecord(paths);
223
- const pendingApprovals = await listApprovalRequests(paths);
256
+ const rpcSnapshot = await tryRpcSnapshot(paths);
257
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
258
+ const pendingApprovals = rpcSnapshot?.approvals.filter((item)=>item.status === "pending") ?? await listApprovalRequests(paths);
224
259
  const heartbeatAgeMs = sessionHeartbeatAgeMs(session);
225
260
  const payload = {
226
261
  id: session.id,
227
262
  status: session.status,
228
263
  repoRoot: session.repoRoot,
264
+ socketPath: session.socketPath,
229
265
  goal: session.goal,
230
266
  daemonPid: session.daemonPid,
231
267
  daemonHeartbeatAt: session.daemonHeartbeatAt,
232
268
  daemonHealthy: isSessionLive(session),
269
+ rpcConnected: rpcSnapshot !== null,
233
270
  heartbeatAgeMs,
234
271
  runtime: session.runtime,
235
272
  taskCounts: {
@@ -243,6 +280,12 @@ async function commandStatus(cwd, args) {
243
280
  approvalCounts: {
244
281
  pending: pendingApprovals.length
245
282
  },
283
+ decisionCounts: {
284
+ total: session.decisions.length
285
+ },
286
+ pathClaimCounts: {
287
+ active: session.pathClaims.filter((claim)=>claim.status === "active").length
288
+ },
246
289
  worktrees: session.worktrees
247
290
  };
248
291
  if (args.includes("--json")) {
@@ -252,12 +295,15 @@ async function commandStatus(cwd, args) {
252
295
  console.log(`Session: ${payload.id}`);
253
296
  console.log(`Status: ${payload.status}${payload.daemonHealthy ? " (healthy)" : " (stale or stopped)"}`);
254
297
  console.log(`Repo: ${payload.repoRoot}`);
298
+ console.log(`Control: ${payload.socketPath}${payload.rpcConnected ? " (connected)" : " (disconnected)"}`);
255
299
  console.log(`Goal: ${payload.goal ?? "-"}`);
256
300
  console.log(`Daemon PID: ${payload.daemonPid ?? "-"}`);
257
301
  console.log(`Heartbeat: ${payload.daemonHeartbeatAt ?? "-"}${heartbeatAgeMs === null ? "" : ` (${heartbeatAgeMs} ms ago)`}`);
258
302
  console.log(`Runtime: node=${payload.runtime.nodeExecutable} codex=${payload.runtime.codexExecutable} claude=${payload.runtime.claudeExecutable}`);
259
303
  console.log(`Tasks: total=${payload.taskCounts.total} pending=${payload.taskCounts.pending} running=${payload.taskCounts.running} blocked=${payload.taskCounts.blocked} completed=${payload.taskCounts.completed} failed=${payload.taskCounts.failed}`);
260
304
  console.log(`Approvals: pending=${payload.approvalCounts.pending}`);
305
+ console.log(`Decisions: total=${payload.decisionCounts.total}`);
306
+ console.log(`Path claims: active=${payload.pathClaimCounts.active}`);
261
307
  for (const worktree of payload.worktrees){
262
308
  console.log(`- ${worktree.agent}: ${worktree.path}`);
263
309
  }
@@ -278,6 +324,7 @@ async function commandPaths(cwd, args) {
278
324
  eventsFile: paths.eventsFile,
279
325
  approvalsFile: paths.approvalsFile,
280
326
  commandsFile: paths.commandsFile,
327
+ socketPath: paths.socketPath,
281
328
  runsDir: paths.runsDir,
282
329
  claudeSettingsFile: paths.claudeSettingsFile,
283
330
  homeApprovalRulesFile: paths.homeApprovalRulesFile,
@@ -298,6 +345,7 @@ async function commandPaths(cwd, args) {
298
345
  console.log(`Events file: ${payload.eventsFile}`);
299
346
  console.log(`Approvals file: ${payload.approvalsFile}`);
300
347
  console.log(`Command queue: ${payload.commandsFile}`);
348
+ console.log(`Control socket: ${payload.socketPath}`);
301
349
  console.log(`Task artifacts: ${payload.runsDir}`);
302
350
  console.log(`Claude settings: ${payload.claudeSettingsFile}`);
303
351
  console.log(`Approval rules: ${payload.homeApprovalRulesFile}`);
@@ -305,26 +353,52 @@ async function commandPaths(cwd, args) {
305
353
  }
306
354
  async function commandTask(cwd, args) {
307
355
  const { paths } = await requireSession(cwd);
308
- const session = await loadSessionRecord(paths);
356
+ const rpcSnapshot = await tryRpcSnapshot(paths);
357
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
309
358
  const requestedAgent = getFlag(args, "--agent");
310
359
  const prompt = getGoal(args.filter((arg, index)=>arg !== "--agent" && args[index - 1] !== "--agent"));
311
360
  if (!prompt) {
312
361
  throw new Error("A task prompt is required. Example: kavi task --agent auto \"Build auth route\"");
313
362
  }
314
- const owner = requestedAgent === "codex" || requestedAgent === "claude" ? requestedAgent : routePrompt(prompt, session.config);
315
- await appendCommand(paths, "enqueue", {
316
- owner,
317
- prompt
318
- });
363
+ const routeDecision = requestedAgent === "codex" || requestedAgent === "claude" ? {
364
+ owner: requestedAgent,
365
+ strategy: "manual",
366
+ confidence: 1,
367
+ reason: `User explicitly assigned the task to ${requestedAgent}.`,
368
+ claimedPaths: extractPromptPathHints(prompt)
369
+ } : await routeTask(prompt, session, paths);
370
+ if (rpcSnapshot) {
371
+ await rpcEnqueueTask(paths, {
372
+ owner: routeDecision.owner,
373
+ prompt,
374
+ routeReason: routeDecision.reason,
375
+ claimedPaths: routeDecision.claimedPaths,
376
+ routeStrategy: routeDecision.strategy,
377
+ routeConfidence: routeDecision.confidence
378
+ });
379
+ } else {
380
+ await appendCommand(paths, "enqueue", {
381
+ owner: routeDecision.owner,
382
+ prompt,
383
+ routeReason: routeDecision.reason,
384
+ claimedPaths: routeDecision.claimedPaths,
385
+ routeStrategy: routeDecision.strategy,
386
+ routeConfidence: routeDecision.confidence
387
+ });
388
+ }
319
389
  await recordEvent(paths, session.id, "task.cli_enqueued", {
320
- owner,
321
- prompt
390
+ owner: routeDecision.owner,
391
+ prompt,
392
+ strategy: routeDecision.strategy,
393
+ confidence: routeDecision.confidence,
394
+ claimedPaths: routeDecision.claimedPaths
322
395
  });
323
- console.log(`Queued task for ${owner}: ${prompt}`);
396
+ console.log(`Queued task for ${routeDecision.owner}: ${prompt}\nRoute: ${routeDecision.strategy} (${routeDecision.confidence.toFixed(2)}) ${routeDecision.reason}`);
324
397
  }
325
398
  async function commandTasks(cwd, args) {
326
399
  const { paths } = await requireSession(cwd);
327
- const session = await loadSessionRecord(paths);
400
+ const rpcSnapshot = await tryRpcSnapshot(paths);
401
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
328
402
  const artifacts = await listTaskArtifacts(paths);
329
403
  const artifactMap = new Map(artifacts.map((artifact)=>[
330
404
  artifact.taskId,
@@ -338,6 +412,8 @@ async function commandTasks(cwd, args) {
338
412
  createdAt: task.createdAt,
339
413
  updatedAt: task.updatedAt,
340
414
  summary: task.summary,
415
+ routeReason: task.routeReason,
416
+ claimedPaths: task.claimedPaths,
341
417
  hasArtifact: artifactMap.has(task.id)
342
418
  }));
343
419
  if (args.includes("--json")) {
@@ -348,6 +424,8 @@ async function commandTasks(cwd, args) {
348
424
  console.log(`${task.id} | ${task.owner} | ${task.status} | artifact=${task.hasArtifact ? "yes" : "no"}`);
349
425
  console.log(` title: ${task.title}`);
350
426
  console.log(` updated: ${task.updatedAt}`);
427
+ console.log(` route: ${task.routeReason ?? "-"}`);
428
+ console.log(` paths: ${task.claimedPaths.join(", ") || "-"}`);
351
429
  console.log(` summary: ${task.summary ?? "-"}`);
352
430
  }
353
431
  }
@@ -367,12 +445,13 @@ function resolveRequestedTaskId(args, knownTaskIds) {
367
445
  }
368
446
  async function commandTaskOutput(cwd, args) {
369
447
  const { paths } = await requireSession(cwd);
370
- const session = await loadSessionRecord(paths);
448
+ const rpcSnapshot = await tryRpcSnapshot(paths);
449
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
371
450
  const sortedTasks = [
372
451
  ...session.tasks
373
452
  ].sort((left, right)=>left.updatedAt.localeCompare(right.updatedAt));
374
453
  const taskId = resolveRequestedTaskId(args, sortedTasks.map((task)=>task.id));
375
- const artifact = await loadTaskArtifact(paths, taskId);
454
+ const artifact = rpcSnapshot ? await rpcTaskArtifact(paths, taskId) : await loadTaskArtifact(paths, taskId);
376
455
  if (!artifact) {
377
456
  throw new Error(`No task artifact found for ${taskId}.`);
378
457
  }
@@ -386,15 +465,65 @@ async function commandTaskOutput(cwd, args) {
386
465
  console.log(`Started: ${artifact.startedAt}`);
387
466
  console.log(`Finished: ${artifact.finishedAt}`);
388
467
  console.log(`Summary: ${artifact.summary ?? "-"}`);
468
+ console.log(`Route: ${artifact.routeReason ?? "-"}`);
469
+ console.log(`Claimed paths: ${artifact.claimedPaths.join(", ") || "-"}`);
389
470
  console.log(`Error: ${artifact.error ?? "-"}`);
471
+ console.log("Decision Replay:");
472
+ for (const line of artifact.decisionReplay){
473
+ console.log(line);
474
+ }
390
475
  console.log("Envelope:");
391
476
  console.log(JSON.stringify(artifact.envelope, null, 2));
392
477
  console.log("Raw Output:");
393
478
  console.log(artifact.rawOutput ?? "");
394
479
  }
480
+ async function commandDecisions(cwd, args) {
481
+ const { paths } = await requireSession(cwd);
482
+ const rpcSnapshot = await tryRpcSnapshot(paths);
483
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
484
+ const limitArg = getFlag(args, "--limit");
485
+ const limit = limitArg ? Number(limitArg) : 20;
486
+ const decisions = [
487
+ ...session.decisions
488
+ ].sort((left, right)=>left.createdAt.localeCompare(right.createdAt)).slice(-Math.max(1, Number.isFinite(limit) ? limit : 20));
489
+ if (args.includes("--json")) {
490
+ console.log(JSON.stringify(decisions, null, 2));
491
+ return;
492
+ }
493
+ if (decisions.length === 0) {
494
+ console.log("No decisions recorded.");
495
+ return;
496
+ }
497
+ for (const decision of decisions){
498
+ console.log(`${decision.createdAt} | ${decision.kind} | ${decision.agent ?? "-"} | ${decision.summary}`);
499
+ console.log(` task: ${decision.taskId ?? "-"}`);
500
+ console.log(` detail: ${decision.detail}`);
501
+ }
502
+ }
503
+ async function commandClaims(cwd, args) {
504
+ const { paths } = await requireSession(cwd);
505
+ const rpcSnapshot = await tryRpcSnapshot(paths);
506
+ const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
507
+ const claims = args.includes("--all") ? session.pathClaims : session.pathClaims.filter((claim)=>claim.status === "active");
508
+ if (args.includes("--json")) {
509
+ console.log(JSON.stringify(claims, null, 2));
510
+ return;
511
+ }
512
+ if (claims.length === 0) {
513
+ console.log("No path claims recorded.");
514
+ return;
515
+ }
516
+ for (const claim of claims){
517
+ console.log(`${claim.id} | ${claim.agent} | ${claim.status} | ${claim.source} | ${claim.paths.join(", ") || "-"}`);
518
+ console.log(` task: ${claim.taskId}`);
519
+ console.log(` updated: ${claim.updatedAt}`);
520
+ console.log(` note: ${claim.note ?? "-"}`);
521
+ }
522
+ }
395
523
  async function commandApprovals(cwd, args) {
396
524
  const { paths } = await requireSession(cwd);
397
- const requests = await listApprovalRequests(paths, {
525
+ const rpcSnapshot = await tryRpcSnapshot(paths);
526
+ const requests = rpcSnapshot ? rpcSnapshot.approvals.filter((request)=>args.includes("--all") || request.status === "pending") : await listApprovalRequests(paths, {
398
527
  includeResolved: args.includes("--all")
399
528
  });
400
529
  if (args.includes("--json")) {
@@ -425,28 +554,54 @@ function resolveApprovalRequestId(requests, requested) {
425
554
  }
426
555
  async function commandResolveApproval(cwd, args, decision) {
427
556
  const { paths } = await requireSession(cwd);
428
- const requests = await listApprovalRequests(paths, {
557
+ const rpcSnapshot = await tryRpcSnapshot(paths);
558
+ const requests = rpcSnapshot?.approvals ?? await listApprovalRequests(paths, {
429
559
  includeResolved: true
430
560
  });
431
561
  const requestedId = args.find((arg)=>!arg.startsWith("--")) ?? "latest";
432
562
  const requestId = resolveApprovalRequestId(requests, requestedId);
433
563
  const remember = args.includes("--remember");
434
- const request = await resolveApprovalRequest(paths, requestId, decision, remember);
435
- const session = await loadSessionRecord(paths);
436
- await recordEvent(paths, session.id, "approval.resolved", {
437
- requestId: request.id,
438
- decision,
439
- remember,
440
- agent: request.agent,
441
- toolName: request.toolName
442
- });
564
+ const request = requests.find((item)=>item.id === requestId);
565
+ if (!request) {
566
+ throw new Error(`Approval request ${requestId} not found.`);
567
+ }
568
+ if (rpcSnapshot) {
569
+ await rpcResolveApproval(paths, {
570
+ requestId,
571
+ decision,
572
+ remember
573
+ });
574
+ } else {
575
+ const resolved = await resolveApprovalRequest(paths, requestId, decision, remember);
576
+ const session = await loadSessionRecord(paths);
577
+ addDecisionRecord(session, {
578
+ kind: "approval",
579
+ agent: resolved.agent,
580
+ summary: `${decision === "allow" ? "Approved" : "Denied"} ${resolved.toolName}`,
581
+ detail: resolved.summary,
582
+ metadata: {
583
+ requestId: resolved.id,
584
+ remember,
585
+ toolName: resolved.toolName
586
+ }
587
+ });
588
+ await saveSessionRecord(paths, session);
589
+ await recordEvent(paths, session.id, "approval.resolved", {
590
+ requestId: resolved.id,
591
+ decision,
592
+ remember,
593
+ agent: resolved.agent,
594
+ toolName: resolved.toolName
595
+ });
596
+ }
443
597
  console.log(`${decision === "allow" ? "Approved" : "Denied"} ${request.id}: ${request.summary}${remember ? " (remembered)" : ""}`);
444
598
  }
445
599
  async function commandEvents(cwd, args) {
446
600
  const { paths } = await requireSession(cwd);
447
601
  const limitArg = getFlag(args, "--limit");
448
602
  const limit = limitArg ? Number(limitArg) : 20;
449
- const events = await readRecentEvents(paths, Number.isFinite(limit) ? limit : 20);
603
+ const rpcSnapshot = await tryRpcSnapshot(paths);
604
+ const events = rpcSnapshot ? await rpcRecentEvents(paths, Number.isFinite(limit) ? limit : 20) : await readRecentEvents(paths, Number.isFinite(limit) ? limit : 20);
450
605
  for (const event of events){
451
606
  console.log(`${event.timestamp} ${event.type} ${JSON.stringify(event.payload)}`);
452
607
  }
@@ -461,8 +616,12 @@ async function commandStop(cwd) {
461
616
  console.log(`Marked stale Kavi session ${session.id} as stopped`);
462
617
  return;
463
618
  }
464
- await appendCommand(paths, "shutdown", {});
465
- await recordEvent(paths, session.id, "daemon.stop_requested", {});
619
+ if (await pingRpc(paths)) {
620
+ await rpcShutdown(paths);
621
+ } else {
622
+ await appendCommand(paths, "shutdown", {});
623
+ await recordEvent(paths, session.id, "daemon.stop_requested", {});
624
+ }
466
625
  await waitForSession(paths, "stopped");
467
626
  console.log(`Stopped Kavi session ${session.id}`);
468
627
  }
@@ -471,6 +630,51 @@ async function commandLand(cwd) {
471
630
  const paths = resolveAppPaths(repoRoot);
472
631
  const session = await loadSessionRecord(paths);
473
632
  const targetBranch = await resolveTargetBranch(repoRoot, session.config.baseBranch);
633
+ const overlappingPaths = await findOverlappingWorktreePaths(session.worktrees, session.baseCommit);
634
+ if (overlappingPaths.length > 0) {
635
+ const existing = session.tasks.find((task)=>task.status === "pending" && task.title === "Resolve integration overlap");
636
+ if (!existing) {
637
+ const taskId = `integration-${Date.now()}`;
638
+ session.tasks.push(buildAdHocTask("codex", [
639
+ "Resolve overlapping worktree changes before landing.",
640
+ `Target branch: ${targetBranch}`,
641
+ "Overlapping paths:",
642
+ ...overlappingPaths.map((item)=>`- ${item}`)
643
+ ].join("\n"), taskId, {
644
+ title: "Resolve integration overlap",
645
+ routeReason: "Created by kavi land because multiple agents changed the same paths.",
646
+ claimedPaths: overlappingPaths
647
+ }));
648
+ upsertPathClaim(session, {
649
+ taskId,
650
+ agent: "codex",
651
+ source: "integration",
652
+ paths: overlappingPaths,
653
+ note: "Integration overlap detected during landing."
654
+ });
655
+ }
656
+ addDecisionRecord(session, {
657
+ kind: "integration",
658
+ agent: "codex",
659
+ summary: "Landing blocked by overlapping worktree paths",
660
+ detail: overlappingPaths.join(", "),
661
+ metadata: {
662
+ targetBranch,
663
+ overlappingPaths
664
+ }
665
+ });
666
+ await saveSessionRecord(paths, session);
667
+ await recordEvent(paths, session.id, "land.overlap_detected", {
668
+ targetBranch,
669
+ overlappingPaths
670
+ });
671
+ console.log("Landing blocked because both agent worktrees changed overlapping paths.");
672
+ console.log("Queued integration task for codex:");
673
+ for (const filePath of overlappingPaths){
674
+ console.log(`- ${filePath}`);
675
+ }
676
+ return;
677
+ }
474
678
  const result = await landBranches(repoRoot, targetBranch, session.worktrees, session.config.validationCommand, session.id, paths.integrationRoot);
475
679
  await recordEvent(paths, session.id, "land.completed", {
476
680
  targetBranch,
@@ -517,6 +721,7 @@ async function commandHook(args) {
517
721
  toolName: descriptor.toolName,
518
722
  summary: descriptor.summary
519
723
  });
724
+ await notifyOperatorSurface(paths, "approval.auto_allowed");
520
725
  console.log(JSON.stringify({
521
726
  continue: true,
522
727
  suppressOutput: true,
@@ -541,6 +746,7 @@ async function commandHook(args) {
541
746
  decision: rule.decision,
542
747
  summary: descriptor.summary
543
748
  });
749
+ await notifyOperatorSurface(paths, "approval.auto_decided");
544
750
  console.log(JSON.stringify({
545
751
  continue: true,
546
752
  suppressOutput: true,
@@ -565,6 +771,7 @@ async function commandHook(args) {
565
771
  toolName: request.toolName,
566
772
  summary: request.summary
567
773
  });
774
+ await notifyOperatorSurface(paths, "approval.requested");
568
775
  const resolved = await waitForApprovalDecision(paths, request.id);
569
776
  const approved = resolved?.status === "approved";
570
777
  const denied = resolved?.status === "denied";
@@ -573,6 +780,7 @@ async function commandHook(args) {
573
780
  requestId: request.id,
574
781
  outcome: approved ? "approved" : denied ? "denied" : "expired"
575
782
  });
783
+ await notifyOperatorSurface(paths, "approval.completed");
576
784
  console.log(JSON.stringify({
577
785
  continue: true,
578
786
  suppressOutput: true,
@@ -586,6 +794,7 @@ async function commandHook(args) {
586
794
  }
587
795
  if (session) {
588
796
  await recordEvent(paths, session.id, "claude.hook", hookPayload);
797
+ await notifyOperatorSurface(paths, "claude.hook");
589
798
  }
590
799
  console.log(JSON.stringify({
591
800
  continue: true
@@ -630,6 +839,12 @@ async function main() {
630
839
  case "task-output":
631
840
  await commandTaskOutput(cwd, args);
632
841
  break;
842
+ case "decisions":
843
+ await commandDecisions(cwd, args);
844
+ break;
845
+ case "claims":
846
+ await commandClaims(cwd, args);
847
+ break;
633
848
  case "approvals":
634
849
  await commandApprovals(cwd, args);
635
850
  break;
package/dist/paths.js CHANGED
@@ -23,7 +23,7 @@ export function resolveAppPaths(repoRoot) {
23
23
  approvalsFile: path.join(stateDir, "approvals.json"),
24
24
  commandsFile: path.join(runtimeDir, "commands.jsonl"),
25
25
  claudeSettingsFile: path.join(runtimeDir, "claude.settings.json"),
26
- socketPath: path.join(runtimeDir, "kavid.sock"),
26
+ socketPath: path.join(homeStateDir, "sockets", `${safeRepoId}.sock`),
27
27
  homeConfigDir,
28
28
  homeConfigFile: path.join(homeConfigDir, "config.toml"),
29
29
  homeApprovalRulesFile: path.join(homeConfigDir, "approval-rules.json"),
@@ -0,0 +1,13 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileExists } from "./fs.js";
4
+ export async function loadAgentPrompt(paths, agent) {
5
+ const promptPath = path.join(paths.promptsDir, `${agent}.md`);
6
+ if (!await fileExists(promptPath)) {
7
+ return "";
8
+ }
9
+ return (await fs.readFile(promptPath, "utf8")).trim();
10
+ }
11
+
12
+
13
+ //# sourceURL=prompts.ts