@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/README.md CHANGED
@@ -7,13 +7,15 @@ Current capabilities:
7
7
  - `kavi init --home`: also scaffold the user-local config file used for binary overrides.
8
8
  - `kavi doctor`: verify Node, Codex, Claude, git worktree support, and local readiness.
9
9
  - `kavi start`: start a managed session without attaching the TUI.
10
- - `kavi open`: create a managed session with separate Codex and Claude worktrees and open the terminal dashboard.
11
- - `kavi resume`: reopen the dashboard for the current repo session.
10
+ - `kavi open`: create a managed session with separate Codex and Claude worktrees and open the full-screen operator console.
11
+ - `kavi resume`: reopen the operator console for the current repo session.
12
12
  - `kavi status`: inspect session health and task counts from any terminal.
13
13
  - `kavi paths`: inspect resolved repo-local, user-local, worktree, and runtime paths.
14
14
  - `kavi task`: enqueue a task for `codex`, `claude`, or `auto` routing.
15
15
  - `kavi tasks`: inspect the session task list with summaries and artifact availability.
16
16
  - `kavi task-output`: inspect the normalized envelope and raw output for a completed task.
17
+ - `kavi decisions`: inspect the persisted routing, approval, task, and integration decisions.
18
+ - `kavi claims`: inspect active or historical path claims.
17
19
  - `kavi approvals`: inspect the approval inbox.
18
20
  - `kavi approve` and `kavi deny`: resolve a pending approval request, optionally with `--remember`.
19
21
  - `kavi events`: inspect recent daemon and task events.
@@ -25,7 +27,9 @@ Runtime model:
25
27
  - Machine-local state defaults to `~/.config/kavi` and `~/.local/state/kavi`.
26
28
  - In restricted environments you can override those with `KAVI_HOME_CONFIG_DIR` and `KAVI_HOME_STATE_DIR`.
27
29
  - User-local runtime overrides live in `~/.config/kavi/config.toml` and can point Kavi at custom `node`, `codex`, and `claude` binaries.
30
+ - The operator surface talks to the daemon over a local control socket under the machine-local state root.
28
31
  - SQLite event mirroring is opt-in with `KAVI_ENABLE_SQLITE_HISTORY=1`; the default event log is JSONL under `.kavi/state/events.jsonl`.
32
+ - The operator console exposes a task board, dual agent lanes, a live inspector pane, approval actions, inline task composition, and worktree diff review with file and hunk navigation.
29
33
 
30
34
  Development:
31
35
 
@@ -38,14 +42,19 @@ node bin/kavi.js status
38
42
  node bin/kavi.js task --agent auto "Design the dashboard shell"
39
43
  node bin/kavi.js tasks
40
44
  node bin/kavi.js task-output latest
45
+ node bin/kavi.js decisions
46
+ node bin/kavi.js claims
41
47
  node bin/kavi.js approvals
42
48
  node bin/kavi.js open
43
- node --experimental-strip-types --test src/**/*.test.ts
49
+ npm test
44
50
  ```
45
51
 
46
52
  Notes:
47
- - The current managed task runners use the installed `codex` and `claude` CLIs directly.
48
- - The dashboard uses a file-backed control loop instead of local sockets so it can run in restricted shells and sandboxes.
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
+ - Claude still runs through the installed `claude` CLI with Kavi-managed hooks and approval decisions.
55
+ - 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.
56
+ - The socket is machine-local rather than repo-local to avoid Unix socket path-length issues in deep repos and temp directories.
57
+ - The console is keyboard-driven: `1-7` switch views, `j/k` move selection, `[` and `]` cycle task detail sections, `,` and `.` cycle changed files, `{` and `}` cycle patch hunks, `y/n` resolve approvals, and `c` opens the inline task composer.
49
58
 
50
59
  Local install options:
51
60
 
@@ -63,17 +72,24 @@ npm link
63
72
  Publishing for testers:
64
73
 
65
74
  ```bash
75
+ # interactive publish flow: prompts for version, defaults to the next patch,
76
+ # runs release checks, then publishes the beta tag
77
+ npm run publish
78
+
66
79
  # authenticate once
67
80
  npm login
68
81
 
69
82
  # verify the package before publish
70
83
  npm run release:check
71
84
 
72
- # publish a beta that friends can install immediately
85
+ # prompt for version and publish beta explicitly
73
86
  npm run publish:beta
74
87
 
75
- # later, publish the stable tag
88
+ # prompt for version and publish stable
76
89
  npm run publish:latest
90
+
91
+ # test the publish flow without sending anything to npm
92
+ npm run publish -- --dry-run
77
93
  ```
78
94
 
79
95
  Install commands for testers:
@@ -89,6 +105,8 @@ npx @mandipadk7/kavi@beta help
89
105
  Notes on publish:
90
106
  - The package name is scoped as `@mandipadk7/kavi` to match the npm user `mandipadk7`.
91
107
  - The publish flow now builds a compiled `dist/` directory first, and the installed CLI prefers `dist/main.js`. Source mode remains available for local development.
108
+ - Hook commands now invoke the compiled entrypoint directly when `dist/` is present, and only use `--experimental-strip-types` in source mode.
109
+ - The interactive publish script strips `repository`, `homepage`, and `bugs` from the packaged `package.json`, so the npm page uses the bundled `README.md` instead of private GitHub links.
92
110
  - `prepublishOnly` runs the release checks automatically during publish.
93
111
 
94
112
  User-local config example:
@@ -1,7 +1,82 @@
1
1
  import fs from "node:fs/promises";
2
- import { buildEnvelopeInstruction, buildPeerMessages, buildSharedContext, extractJsonObject } from "./shared.js";
2
+ import { buildAgentInstructions, buildPeerMessages, buildTaskPrompt, extractJsonObject } from "./shared.js";
3
3
  import { runCommand } from "../process.js";
4
+ import { loadAgentPrompt } from "../prompts.js";
4
5
  import { buildKaviShellCommand } from "../runtime.js";
6
+ const CLAUDE_ENVELOPE_SCHEMA = JSON.stringify({
7
+ type: "object",
8
+ additionalProperties: false,
9
+ required: [
10
+ "summary",
11
+ "status",
12
+ "blockers",
13
+ "nextRecommendation",
14
+ "peerMessages"
15
+ ],
16
+ properties: {
17
+ summary: {
18
+ type: "string"
19
+ },
20
+ status: {
21
+ type: "string",
22
+ enum: [
23
+ "completed",
24
+ "blocked",
25
+ "needs_review"
26
+ ]
27
+ },
28
+ blockers: {
29
+ type: "array",
30
+ items: {
31
+ type: "string"
32
+ }
33
+ },
34
+ nextRecommendation: {
35
+ type: [
36
+ "string",
37
+ "null"
38
+ ]
39
+ },
40
+ peerMessages: {
41
+ type: "array",
42
+ items: {
43
+ type: "object",
44
+ additionalProperties: false,
45
+ required: [
46
+ "to",
47
+ "intent",
48
+ "subject",
49
+ "body"
50
+ ],
51
+ properties: {
52
+ to: {
53
+ type: "string",
54
+ enum: [
55
+ "codex",
56
+ "claude"
57
+ ]
58
+ },
59
+ intent: {
60
+ type: "string",
61
+ enum: [
62
+ "question",
63
+ "handoff",
64
+ "review_request",
65
+ "blocked",
66
+ "context_share"
67
+ ]
68
+ },
69
+ subject: {
70
+ type: "string"
71
+ },
72
+ body: {
73
+ type: "string"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ });
5
80
  function findWorktree(session, agent) {
6
81
  const worktree = session.worktrees.find((item)=>item.agent === agent);
7
82
  if (!worktree) {
@@ -9,6 +84,47 @@ function findWorktree(session, agent) {
9
84
  }
10
85
  return worktree;
11
86
  }
87
+ function asObject(value) {
88
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
89
+ }
90
+ function asString(value) {
91
+ return typeof value === "string" ? value : null;
92
+ }
93
+ function normalizeEnvelope(value) {
94
+ if (value && typeof value === "object" && !Array.isArray(value)) {
95
+ const parsed = value;
96
+ return {
97
+ summary: typeof parsed.summary === "string" ? parsed.summary : "",
98
+ status: parsed.status === "blocked" || parsed.status === "needs_review" ? parsed.status : "completed",
99
+ blockers: Array.isArray(parsed.blockers) ? parsed.blockers.map((item)=>String(item)) : [],
100
+ nextRecommendation: parsed.nextRecommendation === null || typeof parsed.nextRecommendation === "string" ? parsed.nextRecommendation : null,
101
+ peerMessages: Array.isArray(parsed.peerMessages) ? parsed.peerMessages.map((message)=>{
102
+ const payload = asObject(message);
103
+ return {
104
+ to: payload.to === "codex" ? "codex" : "claude",
105
+ intent: payload.intent === "handoff" || payload.intent === "review_request" || payload.intent === "blocked" || payload.intent === "context_share" ? payload.intent : "question",
106
+ subject: String(payload.subject ?? ""),
107
+ body: String(payload.body ?? "")
108
+ };
109
+ }) : []
110
+ };
111
+ }
112
+ return extractJsonObject(String(value ?? ""));
113
+ }
114
+ export function parseClaudeStructuredOutput(rawOutput, fallbackSessionId) {
115
+ const parsed = JSON.parse(rawOutput);
116
+ const wrapper = Array.isArray(parsed) ? asObject(parsed[parsed.length - 1]) : asObject(parsed);
117
+ const sessionId = asString(wrapper.session_id) ?? asString(wrapper.sessionId) ?? fallbackSessionId;
118
+ const resultPayload = "result" in wrapper ? wrapper.result : "content" in wrapper ? wrapper.content : parsed;
119
+ if (wrapper.is_error === true) {
120
+ throw new Error(asString(wrapper.result) ?? "Claude returned an error response.");
121
+ }
122
+ return {
123
+ envelope: normalizeEnvelope(resultPayload),
124
+ sessionId,
125
+ raw: rawOutput
126
+ };
127
+ }
12
128
  export async function writeClaudeSettings(paths, session) {
13
129
  const buildHookCommand = (event)=>buildKaviShellCommand(session.runtime, [
14
130
  "__hook",
@@ -88,33 +204,40 @@ export async function writeClaudeSettings(paths, session) {
88
204
  }
89
205
  export async function runClaudeTask(session, task, paths) {
90
206
  const worktree = findWorktree(session, "claude");
91
- const claudeSessionId = `${session.id}-claude`;
207
+ const claudeSessionId = session.agentStatus.claude.sessionId ?? `${session.id}-claude`;
92
208
  await writeClaudeSettings(paths, session);
209
+ const repoPrompt = await loadAgentPrompt(paths, "claude");
93
210
  const prompt = [
94
- buildSharedContext(session, task, "claude"),
211
+ buildAgentInstructions("claude", worktree.path, repoPrompt),
95
212
  "",
96
- `User goal or prompt:\n${task.prompt}`,
97
- "",
98
- buildEnvelopeInstruction("claude", worktree.path)
213
+ buildTaskPrompt(session, task, "claude")
99
214
  ].join("\n");
100
215
  const result = await runCommand(session.runtime.claudeExecutable, [
101
216
  "-p",
102
- "--session-id",
103
- claudeSessionId,
217
+ "--output-format",
218
+ "json",
219
+ "--json-schema",
220
+ CLAUDE_ENVELOPE_SCHEMA,
104
221
  "--settings",
105
222
  paths.claudeSettingsFile,
106
223
  "--permission-mode",
107
224
  "plan",
225
+ ...session.agentStatus.claude.sessionId ? [
226
+ "--resume",
227
+ claudeSessionId
228
+ ] : [
229
+ "--session-id",
230
+ claudeSessionId
231
+ ],
108
232
  prompt
109
233
  ], {
110
234
  cwd: worktree.path
111
235
  });
112
236
  const rawOutput = result.code === 0 ? result.stdout : `${result.stdout}\n${result.stderr}`;
113
- const envelope = extractJsonObject(rawOutput);
114
- return {
115
- envelope,
116
- raw: rawOutput
117
- };
237
+ if (result.code !== 0) {
238
+ throw new Error(rawOutput.trim() || "Claude task failed.");
239
+ }
240
+ return parseClaudeStructuredOutput(rawOutput, claudeSessionId);
118
241
  }
119
242
  export { buildPeerMessages };
120
243
 
@@ -1,7 +1,82 @@
1
- import path from "node:path";
2
- import fs from "node:fs/promises";
3
- import { runCommand } from "../process.js";
4
- import { buildEnvelopeInstruction, buildPeerMessages, buildSharedContext, extractJsonObject } from "./shared.js";
1
+ import { createApprovalRequest, describeCodexApprovalRequest, findApprovalRule, waitForApprovalDecision } from "../approvals.js";
2
+ import { CodexAppServerClient } from "../codex-app-server.js";
3
+ import { loadAgentPrompt } from "../prompts.js";
4
+ import { recordEvent } from "../session.js";
5
+ import { buildAgentInstructions, buildPeerMessages, buildTaskPrompt, extractJsonObject } from "./shared.js";
6
+ const ENVELOPE_OUTPUT_SCHEMA = {
7
+ type: "object",
8
+ additionalProperties: false,
9
+ required: [
10
+ "summary",
11
+ "status",
12
+ "blockers",
13
+ "nextRecommendation",
14
+ "peerMessages"
15
+ ],
16
+ properties: {
17
+ summary: {
18
+ type: "string"
19
+ },
20
+ status: {
21
+ type: "string",
22
+ enum: [
23
+ "completed",
24
+ "blocked",
25
+ "needs_review"
26
+ ]
27
+ },
28
+ blockers: {
29
+ type: "array",
30
+ items: {
31
+ type: "string"
32
+ }
33
+ },
34
+ nextRecommendation: {
35
+ type: [
36
+ "string",
37
+ "null"
38
+ ]
39
+ },
40
+ peerMessages: {
41
+ type: "array",
42
+ items: {
43
+ type: "object",
44
+ additionalProperties: false,
45
+ required: [
46
+ "to",
47
+ "intent",
48
+ "subject",
49
+ "body"
50
+ ],
51
+ properties: {
52
+ to: {
53
+ type: "string",
54
+ enum: [
55
+ "codex",
56
+ "claude"
57
+ ]
58
+ },
59
+ intent: {
60
+ type: "string",
61
+ enum: [
62
+ "question",
63
+ "handoff",
64
+ "review_request",
65
+ "blocked",
66
+ "context_share"
67
+ ]
68
+ },
69
+ subject: {
70
+ type: "string"
71
+ },
72
+ body: {
73
+ type: "string"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ };
5
80
  function findWorktree(session, agent) {
6
81
  const worktree = session.worktrees.find((item)=>item.agent === agent);
7
82
  if (!worktree) {
@@ -9,35 +84,165 @@ function findWorktree(session, agent) {
9
84
  }
10
85
  return worktree;
11
86
  }
87
+ function asObject(value) {
88
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
89
+ }
90
+ function asString(value) {
91
+ return typeof value === "string" ? value : null;
92
+ }
93
+ function supportsSessionApproval(params) {
94
+ return Array.isArray(params.availableDecisions) ? params.availableDecisions.some((value)=>value === "acceptForSession") : false;
95
+ }
96
+ function buildPermissionsGrant(params) {
97
+ const requested = asObject(params.permissions);
98
+ const granted = {};
99
+ if (requested.network !== null && requested.network !== undefined) {
100
+ granted.network = requested.network;
101
+ }
102
+ if (requested.fileSystem !== null && requested.fileSystem !== undefined) {
103
+ granted.fileSystem = requested.fileSystem;
104
+ }
105
+ return granted;
106
+ }
107
+ function buildApprovalResponse(method, params, approved, remember) {
108
+ switch(method){
109
+ case "item/commandExecution/requestApproval":
110
+ return {
111
+ decision: approved ? remember && supportsSessionApproval(params) ? "acceptForSession" : "accept" : "decline"
112
+ };
113
+ case "item/fileChange/requestApproval":
114
+ return {
115
+ decision: approved ? remember ? "acceptForSession" : "accept" : "decline"
116
+ };
117
+ case "item/permissions/requestApproval":
118
+ return {
119
+ permissions: approved ? buildPermissionsGrant(params) : {},
120
+ scope: remember ? "session" : "turn"
121
+ };
122
+ case "execCommandApproval":
123
+ case "applyPatchApproval":
124
+ return {
125
+ decision: approved ? remember ? "approved_for_session" : "approved" : "denied"
126
+ };
127
+ default:
128
+ throw new Error(`Unsupported Codex approval request: ${method}`);
129
+ }
130
+ }
131
+ function buildThreadParams(session, worktree, developerInstructions) {
132
+ const configuredModel = session.config.agents.codex.model.trim();
133
+ return {
134
+ cwd: worktree.path,
135
+ approvalPolicy: "on-request",
136
+ approvalsReviewer: "user",
137
+ sandbox: "workspace-write",
138
+ baseInstructions: "You are Codex inside Kavi. Operate inside the assigned worktree and keep work task-scoped.",
139
+ developerInstructions,
140
+ model: configuredModel || null,
141
+ ephemeral: false,
142
+ experimentalRawEvents: false,
143
+ persistExtendedHistory: true
144
+ };
145
+ }
146
+ async function ensureThread(client, session, paths, worktree, developerInstructions) {
147
+ const threadParams = buildThreadParams(session, worktree, developerInstructions);
148
+ const existingThreadId = session.agentStatus.codex.sessionId;
149
+ if (existingThreadId) {
150
+ try {
151
+ return await client.resumeThread({
152
+ threadId: existingThreadId,
153
+ ...threadParams
154
+ });
155
+ } catch (error) {
156
+ await recordEvent(paths, session.id, "codex.thread_resume_failed", {
157
+ threadId: existingThreadId,
158
+ error: error instanceof Error ? error.message : String(error)
159
+ });
160
+ }
161
+ }
162
+ return await client.startThread(threadParams);
163
+ }
164
+ async function handleCodexApproval(session, paths, request) {
165
+ const descriptor = describeCodexApprovalRequest(request.method, request.params);
166
+ const rule = await findApprovalRule(paths, {
167
+ repoRoot: session.repoRoot,
168
+ agent: "codex",
169
+ toolName: descriptor.toolName,
170
+ matchKey: descriptor.matchKey
171
+ });
172
+ if (rule) {
173
+ await recordEvent(paths, session.id, "approval.auto_decided", {
174
+ agent: "codex",
175
+ requestId: request.id,
176
+ method: request.method,
177
+ toolName: descriptor.toolName,
178
+ summary: descriptor.summary,
179
+ decision: rule.decision
180
+ });
181
+ return buildApprovalResponse(request.method, request.params, rule.decision === "allow", true);
182
+ }
183
+ const approval = await createApprovalRequest(paths, {
184
+ sessionId: session.id,
185
+ repoRoot: session.repoRoot,
186
+ agent: "codex",
187
+ hookEvent: request.method,
188
+ payload: request.params,
189
+ toolName: descriptor.toolName,
190
+ summary: descriptor.summary,
191
+ matchKey: descriptor.matchKey
192
+ });
193
+ await recordEvent(paths, session.id, "approval.requested", {
194
+ requestId: approval.id,
195
+ agent: "codex",
196
+ method: request.method,
197
+ toolName: approval.toolName,
198
+ summary: approval.summary
199
+ });
200
+ const resolved = await waitForApprovalDecision(paths, approval.id);
201
+ const approved = resolved?.status === "approved";
202
+ const remember = resolved?.remember ?? false;
203
+ await recordEvent(paths, session.id, "approval.completed", {
204
+ requestId: approval.id,
205
+ agent: "codex",
206
+ method: request.method,
207
+ outcome: approved ? "approved" : resolved?.status === "denied" ? "denied" : "expired"
208
+ });
209
+ return buildApprovalResponse(request.method, request.params, approved, remember);
210
+ }
12
211
  export async function runCodexTask(session, task, paths) {
13
212
  const worktree = findWorktree(session, "codex");
14
- const outputFile = path.join(paths.runtimeDir, `codex-${task.id}.txt`);
15
- const prompt = [
16
- buildSharedContext(session, task, "codex"),
17
- "",
18
- `User goal or prompt:\n${task.prompt}`,
19
- "",
20
- buildEnvelopeInstruction("codex", worktree.path)
21
- ].join("\n");
22
- const result = await runCommand(session.runtime.codexExecutable, [
23
- "exec",
24
- "--skip-git-repo-check",
25
- "--cd",
26
- worktree.path,
27
- "--sandbox",
28
- "workspace-write",
29
- "--output-last-message",
30
- outputFile,
31
- prompt
32
- ], {
33
- cwd: session.repoRoot
213
+ const repoPrompt = await loadAgentPrompt(paths, "codex");
214
+ const developerInstructions = buildAgentInstructions("codex", worktree.path, repoPrompt);
215
+ const client = new CodexAppServerClient(session.runtime, session.repoRoot, async (request)=>{
216
+ return await handleCodexApproval(session, paths, request);
34
217
  });
35
- const rawOutput = result.code === 0 ? await fs.readFile(outputFile, "utf8") : `${result.stdout}\n${result.stderr}`;
36
- const envelope = extractJsonObject(rawOutput);
37
- return {
38
- envelope,
39
- raw: rawOutput
40
- };
218
+ try {
219
+ await client.initialize();
220
+ 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
+ });
236
+ const rawOutput = `${result.assistantMessage}${result.stderr ? `\n\n[stderr]\n${result.stderr}` : ""}`;
237
+ const envelope = extractJsonObject(result.assistantMessage);
238
+ return {
239
+ envelope,
240
+ raw: rawOutput,
241
+ threadId
242
+ };
243
+ } finally{
244
+ await client.close();
245
+ }
41
246
  }
42
247
  export { buildPeerMessages };
43
248
 
@@ -1,5 +1,21 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { nowIso } from "../paths.js";
3
+ function formatDecisionLine(summary, detail) {
4
+ return detail.trim() ? `- ${summary}: ${detail}` : `- ${summary}`;
5
+ }
6
+ export function buildDecisionReplay(session, task, agent) {
7
+ const taskDecisions = session.decisions.filter((decision)=>decision.taskId === task.id).slice(-6).map((decision)=>formatDecisionLine(`[${decision.kind}] ${decision.summary}`, decision.detail));
8
+ const sharedDecisions = session.decisions.filter((decision)=>decision.taskId !== task.id && (decision.agent === agent || decision.agent === null)).slice(-4).map((decision)=>formatDecisionLine(`[${decision.kind}] ${decision.summary}`, decision.detail));
9
+ const relevantClaims = session.pathClaims.filter((claim)=>claim.status === "active" && (claim.taskId === task.id || claim.agent !== agent)).slice(-6).map((claim)=>`- ${claim.agent} ${claim.source} claim on ${claim.paths.join(", ")}${claim.note ? `: ${claim.note}` : ""}`);
10
+ const replay = [
11
+ `- Current route reason: ${task.routeReason ?? "not recorded"}`,
12
+ `- Current claimed paths: ${task.claimedPaths.join(", ") || "none"}`,
13
+ ...taskDecisions,
14
+ ...sharedDecisions,
15
+ ...relevantClaims
16
+ ];
17
+ return replay.slice(0, 16);
18
+ }
3
19
  export function extractJsonObject(rawOutput) {
4
20
  const trimmed = rawOutput.trim();
5
21
  const fencedMatch = trimmed.match(/```json\s*([\s\S]*?)```/i);
@@ -36,15 +52,28 @@ export function buildPeerMessages(envelope, from, taskId) {
36
52
  export function buildSharedContext(session, task, agent) {
37
53
  const inbox = session.peerMessages.filter((message)=>message.to === agent).slice(-session.config.messageLimit).map((message)=>`- [${message.intent}] ${message.subject}: ${message.body}`).join("\n");
38
54
  const tasks = session.tasks.map((item)=>`- ${item.id} | ${item.owner} | ${item.status} | ${item.title}`).join("\n");
55
+ const decisionReplay = buildDecisionReplay(session, task, agent).join("\n");
56
+ const claims = session.pathClaims.filter((claim)=>claim.status === "active").slice(-6).map((claim)=>`- ${claim.agent} | ${claim.paths.join(", ")}`).join("\n");
39
57
  return [
40
58
  `Session goal: ${session.goal ?? "No goal recorded."}`,
41
59
  `Current task: ${task.title}`,
42
60
  "Task board:",
43
61
  tasks || "- none",
62
+ "Compaction-safe replay:",
63
+ decisionReplay || "- empty",
64
+ "Active path claims:",
65
+ claims || "- empty",
44
66
  `Peer inbox for ${agent}:`,
45
67
  inbox || "- empty"
46
68
  ].join("\n");
47
69
  }
70
+ export function buildTaskPrompt(session, task, agent) {
71
+ return [
72
+ buildSharedContext(session, task, agent),
73
+ "",
74
+ `User goal or prompt:\n${task.prompt}`
75
+ ].join("\n");
76
+ }
48
77
  export function buildEnvelopeInstruction(agent, worktreePath) {
49
78
  const peer = agent === "codex" ? "claude" : "codex";
50
79
  const focus = agent === "codex" ? "Focus on planning, architecture, backend concerns, codebase structure, and implementation risks." : "Focus on intent, user experience, frontend structure, and interaction quality.";
@@ -64,6 +93,12 @@ export function buildEnvelopeInstruction(agent, worktreePath) {
64
93
  "Do not wrap the JSON in Markdown."
65
94
  ].join("\n");
66
95
  }
96
+ export function buildAgentInstructions(agent, worktreePath, repoPrompt) {
97
+ return [
98
+ repoPrompt.trim(),
99
+ buildEnvelopeInstruction(agent, worktreePath)
100
+ ].filter(Boolean).join("\n\n");
101
+ }
67
102
 
68
103
 
69
104
  //# sourceURL=adapters/shared.ts