@mandipadk7/kavi 0.1.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/adapters/claude.js +4 -1
- package/dist/config.js +4 -3
- package/dist/daemon.js +77 -3
- package/dist/git.js +61 -6
- package/dist/main.js +339 -65
- package/dist/paths.js +2 -0
- package/dist/recommendations.js +205 -19
- package/dist/reports.js +118 -0
- package/dist/rpc.js +20 -1
- package/dist/session.js +11 -0
- package/dist/tui.js +354 -37
- package/dist/workflow.js +450 -0
- package/package.json +1 -1
package/dist/recommendations.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildClaimHotspots } from "./decision-ledger.js";
|
|
2
2
|
import { findOwnershipRuleConflicts } from "./ownership.js";
|
|
3
|
+
import { nowIso } from "./paths.js";
|
|
3
4
|
function normalizePath(value) {
|
|
4
5
|
return value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
5
6
|
}
|
|
@@ -11,7 +12,100 @@ function pathMatchesScope(scope, filePath) {
|
|
|
11
12
|
function otherAgent(agent) {
|
|
12
13
|
return agent === "codex" ? "claude" : "codex";
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
function stableSerialize(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return `[${value.map((item)=>stableSerialize(item)).join(",")}]`;
|
|
18
|
+
}
|
|
19
|
+
if (value && typeof value === "object") {
|
|
20
|
+
return `{${Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, item])=>`${JSON.stringify(key)}:${stableSerialize(item)}`).join(",")}}`;
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
function recommendationFingerprint(draft) {
|
|
25
|
+
return stableSerialize({
|
|
26
|
+
id: draft.id,
|
|
27
|
+
kind: draft.kind,
|
|
28
|
+
title: draft.title,
|
|
29
|
+
detail: draft.detail,
|
|
30
|
+
targetAgent: draft.targetAgent,
|
|
31
|
+
filePath: draft.filePath,
|
|
32
|
+
taskIds: [
|
|
33
|
+
...draft.taskIds
|
|
34
|
+
].sort(),
|
|
35
|
+
reviewNoteIds: [
|
|
36
|
+
...draft.reviewNoteIds
|
|
37
|
+
].sort(),
|
|
38
|
+
metadata: draft.metadata
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function recommendationPriority(kind) {
|
|
42
|
+
switch(kind){
|
|
43
|
+
case "integration":
|
|
44
|
+
return 0;
|
|
45
|
+
case "handoff":
|
|
46
|
+
return 1;
|
|
47
|
+
case "ownership-config":
|
|
48
|
+
return 2;
|
|
49
|
+
default:
|
|
50
|
+
return 3;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function recommendationToneSort(left, right) {
|
|
54
|
+
const statusDelta = Number(left.status === "dismissed") - Number(right.status === "dismissed");
|
|
55
|
+
if (statusDelta !== 0) {
|
|
56
|
+
return statusDelta;
|
|
57
|
+
}
|
|
58
|
+
const followUpDelta = Number(right.openFollowUpTaskIds.length > 0) - Number(left.openFollowUpTaskIds.length > 0);
|
|
59
|
+
if (followUpDelta !== 0) {
|
|
60
|
+
return followUpDelta;
|
|
61
|
+
}
|
|
62
|
+
const kindDelta = recommendationPriority(left.kind) - recommendationPriority(right.kind);
|
|
63
|
+
if (kindDelta !== 0) {
|
|
64
|
+
return kindDelta;
|
|
65
|
+
}
|
|
66
|
+
return left.title.localeCompare(right.title);
|
|
67
|
+
}
|
|
68
|
+
function recommendationStateFor(session, draft) {
|
|
69
|
+
return (session.recommendationStates ?? []).find((state)=>state.id === draft.id) ?? null;
|
|
70
|
+
}
|
|
71
|
+
function recommendationOpenFollowUpTaskIds(session, appliedTaskIds) {
|
|
72
|
+
return appliedTaskIds.filter((taskId)=>session.tasks.some((task)=>task.id === taskId && (task.status === "pending" || task.status === "running" || task.status === "blocked")));
|
|
73
|
+
}
|
|
74
|
+
function hydrateRecommendation(session, draft) {
|
|
75
|
+
const fingerprint = recommendationFingerprint(draft);
|
|
76
|
+
const persisted = recommendationStateFor(session, draft);
|
|
77
|
+
const dismissedStillMatches = persisted?.status === "dismissed" && persisted.fingerprint === fingerprint;
|
|
78
|
+
const status = dismissedStillMatches ? "dismissed" : "active";
|
|
79
|
+
const appliedTaskIds = persisted?.appliedTaskIds ?? [];
|
|
80
|
+
const openFollowUpTaskIds = recommendationOpenFollowUpTaskIds(session, appliedTaskIds);
|
|
81
|
+
return {
|
|
82
|
+
...draft,
|
|
83
|
+
fingerprint,
|
|
84
|
+
status,
|
|
85
|
+
dismissedReason: status === "dismissed" ? persisted?.dismissedReason ?? null : null,
|
|
86
|
+
dismissedAt: status === "dismissed" ? persisted?.dismissedAt ?? null : null,
|
|
87
|
+
lastAppliedAt: persisted?.lastAppliedAt ?? null,
|
|
88
|
+
appliedTaskIds,
|
|
89
|
+
openFollowUpTaskIds
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function recommendationMatchesQuery(recommendation, query) {
|
|
93
|
+
const includeDismissed = query.includeDismissed ?? false;
|
|
94
|
+
if (!includeDismissed && recommendation.status === "dismissed") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (query.kind && query.kind !== "all" && recommendation.kind !== query.kind) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (query.targetAgent && query.targetAgent !== "all" && recommendation.targetAgent !== query.targetAgent) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (query.status && query.status !== "all" && recommendation.status !== query.status) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
function buildRecommendationDrafts(session) {
|
|
15
109
|
const recommendations = [];
|
|
16
110
|
const hotspots = buildClaimHotspots(session);
|
|
17
111
|
const ownershipConflicts = findOwnershipRuleConflicts(session.config);
|
|
@@ -112,27 +206,119 @@ export function buildOperatorRecommendations(session) {
|
|
|
112
206
|
deduped.set(recommendation.id, recommendation);
|
|
113
207
|
}
|
|
114
208
|
}
|
|
115
|
-
const priority = (kind)=>{
|
|
116
|
-
switch(kind){
|
|
117
|
-
case "integration":
|
|
118
|
-
return 0;
|
|
119
|
-
case "handoff":
|
|
120
|
-
return 1;
|
|
121
|
-
case "ownership-config":
|
|
122
|
-
return 2;
|
|
123
|
-
default:
|
|
124
|
-
return 3;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
209
|
return [
|
|
128
210
|
...deduped.values()
|
|
129
|
-
]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
export function buildOperatorRecommendations(session, query = {}) {
|
|
214
|
+
return buildRecommendationDrafts(session).map((draft)=>hydrateRecommendation(session, draft)).filter((recommendation)=>recommendationMatchesQuery(recommendation, query)).sort(recommendationToneSort);
|
|
215
|
+
}
|
|
216
|
+
export function findOperatorRecommendation(session, recommendationId) {
|
|
217
|
+
return buildOperatorRecommendations(session, {
|
|
218
|
+
includeDismissed: true
|
|
219
|
+
}).find((recommendation)=>recommendation.id === recommendationId) ?? null;
|
|
220
|
+
}
|
|
221
|
+
function upsertRecommendationState(session, nextState) {
|
|
222
|
+
session.recommendationStates = [
|
|
223
|
+
...(session.recommendationStates ?? []).filter((state)=>state.id !== nextState.id),
|
|
224
|
+
nextState
|
|
225
|
+
].sort((left, right)=>left.id.localeCompare(right.id));
|
|
226
|
+
return nextState;
|
|
227
|
+
}
|
|
228
|
+
export function dismissOperatorRecommendation(session, recommendationId, reason) {
|
|
229
|
+
const recommendation = findOperatorRecommendation(session, recommendationId);
|
|
230
|
+
if (!recommendation) {
|
|
231
|
+
throw new Error(`Recommendation ${recommendationId} was not found.`);
|
|
232
|
+
}
|
|
233
|
+
const timestamp = nowIso();
|
|
234
|
+
upsertRecommendationState(session, {
|
|
235
|
+
id: recommendation.id,
|
|
236
|
+
fingerprint: recommendation.fingerprint,
|
|
237
|
+
status: "dismissed",
|
|
238
|
+
dismissedReason: reason,
|
|
239
|
+
dismissedAt: timestamp,
|
|
240
|
+
lastAppliedAt: recommendation.lastAppliedAt,
|
|
241
|
+
appliedTaskIds: recommendation.appliedTaskIds,
|
|
242
|
+
updatedAt: timestamp
|
|
243
|
+
});
|
|
244
|
+
return findOperatorRecommendation(session, recommendationId) ?? recommendation;
|
|
245
|
+
}
|
|
246
|
+
export function restoreOperatorRecommendation(session, recommendationId) {
|
|
247
|
+
const recommendation = findOperatorRecommendation(session, recommendationId);
|
|
248
|
+
if (!recommendation) {
|
|
249
|
+
throw new Error(`Recommendation ${recommendationId} was not found.`);
|
|
250
|
+
}
|
|
251
|
+
const timestamp = nowIso();
|
|
252
|
+
upsertRecommendationState(session, {
|
|
253
|
+
id: recommendation.id,
|
|
254
|
+
fingerprint: recommendation.fingerprint,
|
|
255
|
+
status: "active",
|
|
256
|
+
dismissedReason: null,
|
|
257
|
+
dismissedAt: null,
|
|
258
|
+
lastAppliedAt: recommendation.lastAppliedAt,
|
|
259
|
+
appliedTaskIds: recommendation.appliedTaskIds,
|
|
260
|
+
updatedAt: timestamp
|
|
135
261
|
});
|
|
262
|
+
return findOperatorRecommendation(session, recommendationId) ?? recommendation;
|
|
263
|
+
}
|
|
264
|
+
export function recordRecommendationApplied(session, recommendationId, taskId) {
|
|
265
|
+
const recommendation = findOperatorRecommendation(session, recommendationId);
|
|
266
|
+
if (!recommendation) {
|
|
267
|
+
throw new Error(`Recommendation ${recommendationId} was not found.`);
|
|
268
|
+
}
|
|
269
|
+
const timestamp = nowIso();
|
|
270
|
+
const appliedTaskIds = recommendation.appliedTaskIds.includes(taskId) ? recommendation.appliedTaskIds : [
|
|
271
|
+
...recommendation.appliedTaskIds,
|
|
272
|
+
taskId
|
|
273
|
+
];
|
|
274
|
+
upsertRecommendationState(session, {
|
|
275
|
+
id: recommendation.id,
|
|
276
|
+
fingerprint: recommendation.fingerprint,
|
|
277
|
+
status: "active",
|
|
278
|
+
dismissedReason: null,
|
|
279
|
+
dismissedAt: null,
|
|
280
|
+
lastAppliedAt: timestamp,
|
|
281
|
+
appliedTaskIds,
|
|
282
|
+
updatedAt: timestamp
|
|
283
|
+
});
|
|
284
|
+
return findOperatorRecommendation(session, recommendationId) ?? recommendation;
|
|
285
|
+
}
|
|
286
|
+
export function buildRecommendationActionPlan(session, recommendationId, options = {}) {
|
|
287
|
+
const recommendation = findOperatorRecommendation(session, recommendationId);
|
|
288
|
+
if (!recommendation) {
|
|
289
|
+
throw new Error(`Recommendation ${recommendationId} was not found.`);
|
|
290
|
+
}
|
|
291
|
+
if (recommendation.kind === "ownership-config") {
|
|
292
|
+
throw new Error(`Recommendation ${recommendation.id} is advisory only. Run "${recommendation.commandHint}" and update the config manually.`);
|
|
293
|
+
}
|
|
294
|
+
if (recommendation.openFollowUpTaskIds.length > 0 && !options.force) {
|
|
295
|
+
throw new Error(`Recommendation ${recommendation.id} already has open follow-up task(s): ${recommendation.openFollowUpTaskIds.join(", ")}. Re-run with --force to enqueue another task.`);
|
|
296
|
+
}
|
|
297
|
+
const owner = recommendation.targetAgent === "operator" || recommendation.targetAgent === null ? "codex" : recommendation.targetAgent;
|
|
298
|
+
const prompt = recommendation.kind === "integration" ? [
|
|
299
|
+
"Coordinate and resolve overlapping agent work before landing.",
|
|
300
|
+
recommendation.detail,
|
|
301
|
+
recommendation.filePath ? `Primary hotspot: ${recommendation.filePath}` : null
|
|
302
|
+
].filter(Boolean).join("\n") : [
|
|
303
|
+
"Pick up ownership-aware handoff work from Kavi.",
|
|
304
|
+
recommendation.detail,
|
|
305
|
+
recommendation.filePath ? `Focus path: ${recommendation.filePath}` : null
|
|
306
|
+
].filter(Boolean).join("\n");
|
|
307
|
+
return {
|
|
308
|
+
recommendation,
|
|
309
|
+
owner,
|
|
310
|
+
prompt,
|
|
311
|
+
routeReason: `Queued from Kavi recommendation ${recommendation.id}.`,
|
|
312
|
+
routeStrategy: "manual",
|
|
313
|
+
routeConfidence: 1,
|
|
314
|
+
claimedPaths: recommendation.filePath ? [
|
|
315
|
+
recommendation.filePath
|
|
316
|
+
] : [],
|
|
317
|
+
routeMetadata: {
|
|
318
|
+
recommendationId: recommendation.id,
|
|
319
|
+
recommendationKind: recommendation.kind
|
|
320
|
+
}
|
|
321
|
+
};
|
|
136
322
|
}
|
|
137
323
|
|
|
138
324
|
|
package/dist/reports.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureDir, fileExists, readJson, writeJson } from "./fs.js";
|
|
4
|
+
function reportPath(paths, reportId) {
|
|
5
|
+
return path.join(paths.reportsDir, `${reportId}.json`);
|
|
6
|
+
}
|
|
7
|
+
function asStringArray(value) {
|
|
8
|
+
return Array.isArray(value) ? value.map((item)=>String(item)) : [];
|
|
9
|
+
}
|
|
10
|
+
function normalizeTaskResult(value) {
|
|
11
|
+
return {
|
|
12
|
+
taskId: String(value.taskId),
|
|
13
|
+
owner: value.owner === "codex" || value.owner === "claude" || value.owner === "router" ? value.owner : "router",
|
|
14
|
+
title: typeof value.title === "string" ? value.title : "",
|
|
15
|
+
summary: typeof value.summary === "string" ? value.summary : "",
|
|
16
|
+
claimedPaths: asStringArray(value.claimedPaths),
|
|
17
|
+
finishedAt: typeof value.finishedAt === "string" ? value.finishedAt : ""
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function normalizeAgentChange(value) {
|
|
21
|
+
return {
|
|
22
|
+
agent: value.agent === "claude" ? "claude" : "codex",
|
|
23
|
+
paths: asStringArray(value.paths)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function normalizeSnapshotCommit(value) {
|
|
27
|
+
return {
|
|
28
|
+
agent: value.agent === "claude" ? "claude" : "codex",
|
|
29
|
+
commit: typeof value.commit === "string" ? value.commit : "",
|
|
30
|
+
createdCommit: value.createdCommit === true
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function normalizeLandReport(report) {
|
|
34
|
+
return {
|
|
35
|
+
id: String(report.id),
|
|
36
|
+
sessionId: String(report.sessionId),
|
|
37
|
+
goal: typeof report.goal === "string" ? report.goal : null,
|
|
38
|
+
createdAt: typeof report.createdAt === "string" ? report.createdAt : "",
|
|
39
|
+
targetBranch: typeof report.targetBranch === "string" ? report.targetBranch : "",
|
|
40
|
+
integrationBranch: typeof report.integrationBranch === "string" ? report.integrationBranch : "",
|
|
41
|
+
integrationPath: typeof report.integrationPath === "string" ? report.integrationPath : "",
|
|
42
|
+
validationCommand: typeof report.validationCommand === "string" ? report.validationCommand : "",
|
|
43
|
+
validationStatus: report.validationStatus === "ran" || report.validationStatus === "skipped" || report.validationStatus === "not_configured" ? report.validationStatus : "not_configured",
|
|
44
|
+
validationDetail: typeof report.validationDetail === "string" ? report.validationDetail : "",
|
|
45
|
+
changedByAgent: Array.isArray(report.changedByAgent) ? report.changedByAgent.map((item)=>normalizeAgentChange(item)) : [],
|
|
46
|
+
completedTasks: Array.isArray(report.completedTasks) ? report.completedTasks.map((item)=>normalizeTaskResult(item)) : [],
|
|
47
|
+
snapshotCommits: Array.isArray(report.snapshotCommits) ? report.snapshotCommits.map((item)=>normalizeSnapshotCommit(item)) : [],
|
|
48
|
+
commandsRun: asStringArray(report.commandsRun),
|
|
49
|
+
reviewThreadsLanded: typeof report.reviewThreadsLanded === "number" ? report.reviewThreadsLanded : 0,
|
|
50
|
+
openReviewThreadsRemaining: typeof report.openReviewThreadsRemaining === "number" ? report.openReviewThreadsRemaining : 0,
|
|
51
|
+
summary: asStringArray(report.summary)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function buildLandReport(params) {
|
|
55
|
+
const changedSummary = params.changedByAgent.map((changeSet)=>`${changeSet.agent}: ${changeSet.paths.length} path(s)`).join(" | ");
|
|
56
|
+
const validation = params.validationCommand.trim() ? params.validationStatus === "ran" ? `Validation ran with "${params.validationCommand.trim()}".` : params.validationStatus === "skipped" ? params.validationDetail : "No validation command was configured." : "No validation command was configured.";
|
|
57
|
+
return {
|
|
58
|
+
id: params.id,
|
|
59
|
+
sessionId: params.sessionId,
|
|
60
|
+
goal: params.goal,
|
|
61
|
+
createdAt: params.createdAt,
|
|
62
|
+
targetBranch: params.targetBranch,
|
|
63
|
+
integrationBranch: params.integrationBranch,
|
|
64
|
+
integrationPath: params.integrationPath,
|
|
65
|
+
validationCommand: params.validationCommand,
|
|
66
|
+
validationStatus: params.validationStatus,
|
|
67
|
+
validationDetail: params.validationDetail,
|
|
68
|
+
changedByAgent: params.changedByAgent.map((item)=>normalizeAgentChange(item)),
|
|
69
|
+
completedTasks: params.completedTasks.map((item)=>normalizeTaskResult(item)),
|
|
70
|
+
snapshotCommits: params.snapshotCommits.map((item)=>normalizeSnapshotCommit(item)),
|
|
71
|
+
commandsRun: [
|
|
72
|
+
...params.commandsRun
|
|
73
|
+
],
|
|
74
|
+
reviewThreadsLanded: params.reviewThreadsLanded,
|
|
75
|
+
openReviewThreadsRemaining: params.openReviewThreadsRemaining,
|
|
76
|
+
summary: [
|
|
77
|
+
`Merged managed work into ${params.targetBranch}.`,
|
|
78
|
+
changedSummary || "No worktree changes were recorded before landing.",
|
|
79
|
+
validation,
|
|
80
|
+
params.reviewThreadsLanded > 0 ? `${params.reviewThreadsLanded} review thread(s) were marked as landed.` : "No review threads were marked as landed."
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function saveLandReport(paths, report) {
|
|
85
|
+
await ensureDir(paths.reportsDir);
|
|
86
|
+
await writeJson(reportPath(paths, report.id), report);
|
|
87
|
+
}
|
|
88
|
+
export async function loadLandReport(paths, reportId) {
|
|
89
|
+
const filePath = reportPath(paths, reportId);
|
|
90
|
+
if (!await fileExists(filePath)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return normalizeLandReport(await readJson(filePath));
|
|
94
|
+
}
|
|
95
|
+
export async function listLandReports(paths) {
|
|
96
|
+
if (!await fileExists(paths.reportsDir)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const entries = await fs.readdir(paths.reportsDir, {
|
|
100
|
+
withFileTypes: true
|
|
101
|
+
});
|
|
102
|
+
const reports = [];
|
|
103
|
+
for (const entry of entries){
|
|
104
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const report = await readJson(path.join(paths.reportsDir, entry.name));
|
|
108
|
+
reports.push(normalizeLandReport(report));
|
|
109
|
+
}
|
|
110
|
+
return reports.sort((left, right)=>right.createdAt.localeCompare(left.createdAt));
|
|
111
|
+
}
|
|
112
|
+
export async function loadLatestLandReport(paths) {
|
|
113
|
+
const reports = await listLandReports(paths);
|
|
114
|
+
return reports[0] ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
//# sourceURL=reports.ts
|
package/dist/rpc.js
CHANGED
|
@@ -99,7 +99,26 @@ export async function rpcEnqueueTask(paths, params) {
|
|
|
99
99
|
routeMetadata: params.routeMetadata,
|
|
100
100
|
claimedPaths: params.claimedPaths,
|
|
101
101
|
routeStrategy: params.routeStrategy,
|
|
102
|
-
routeConfidence: params.routeConfidence
|
|
102
|
+
routeConfidence: params.routeConfidence,
|
|
103
|
+
...params.recommendationId ? {
|
|
104
|
+
recommendationId: params.recommendationId
|
|
105
|
+
} : {},
|
|
106
|
+
...params.recommendationKind ? {
|
|
107
|
+
recommendationKind: params.recommendationKind
|
|
108
|
+
} : {}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export async function rpcDismissRecommendation(paths, params) {
|
|
112
|
+
await sendRpcRequest(paths, "dismissRecommendation", {
|
|
113
|
+
recommendationId: params.recommendationId,
|
|
114
|
+
...params.reason ? {
|
|
115
|
+
reason: params.reason
|
|
116
|
+
} : {}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export async function rpcRestoreRecommendation(paths, params) {
|
|
120
|
+
await sendRpcRequest(paths, "restoreRecommendation", {
|
|
121
|
+
recommendationId: params.recommendationId
|
|
103
122
|
});
|
|
104
123
|
}
|
|
105
124
|
export async function rpcResolveApproval(paths, params) {
|
package/dist/session.js
CHANGED
|
@@ -36,6 +36,7 @@ export async function createSessionRecord(paths, config, runtime, sessionId, bas
|
|
|
36
36
|
decisions: [],
|
|
37
37
|
pathClaims: [],
|
|
38
38
|
reviewNotes: [],
|
|
39
|
+
recommendationStates: [],
|
|
39
40
|
agentStatus: {
|
|
40
41
|
codex: initialAgentStatus("codex", "codex-app-server"),
|
|
41
42
|
claude: initialAgentStatus("claude", "claude-print")
|
|
@@ -85,6 +86,16 @@ export async function loadSessionRecord(paths) {
|
|
|
85
86
|
landedAt: typeof note.landedAt === "string" ? note.landedAt : null,
|
|
86
87
|
followUpTaskIds: Array.isArray(note.followUpTaskIds) ? note.followUpTaskIds.map((item)=>String(item)) : []
|
|
87
88
|
})) : [];
|
|
89
|
+
record.recommendationStates = Array.isArray(record.recommendationStates) ? record.recommendationStates.map((state)=>({
|
|
90
|
+
id: String(state.id),
|
|
91
|
+
fingerprint: typeof state.fingerprint === "string" ? state.fingerprint : "",
|
|
92
|
+
status: state.status === "dismissed" ? "dismissed" : "active",
|
|
93
|
+
dismissedReason: typeof state.dismissedReason === "string" ? state.dismissedReason : null,
|
|
94
|
+
dismissedAt: typeof state.dismissedAt === "string" ? state.dismissedAt : null,
|
|
95
|
+
lastAppliedAt: typeof state.lastAppliedAt === "string" ? state.lastAppliedAt : null,
|
|
96
|
+
appliedTaskIds: Array.isArray(state.appliedTaskIds) ? state.appliedTaskIds.map((item)=>String(item)) : [],
|
|
97
|
+
updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : nowIso()
|
|
98
|
+
})) : [];
|
|
88
99
|
return record;
|
|
89
100
|
}
|
|
90
101
|
export async function saveSessionRecord(paths, record) {
|