@danypops/pi-lector 0.14.0 → 0.15.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/extension/src/cross-workspace-search/model-content.ts +69 -0
- package/extension/src/index.ts +46 -24
- package/extension/src/presentation/tool-presentation.ts +1 -0
- package/extension/src/reference-based-rename/rendering.ts +21 -0
- package/extension/src/workspace-cache/operations.ts +24 -3
- package/extension/src/workspace-cache/rendering.ts +19 -2
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SymbolSearchResult, TextSearchResult } from "@danypops/lector";
|
|
2
|
+
import { boundModelContentText, DEFAULT_MODEL_CONTENT_BYTES } from "../presentation/model-content.ts";
|
|
3
|
+
import type { CrossWorkspaceOutcome } from "./operations.ts";
|
|
4
|
+
|
|
5
|
+
const MAX_RESULTS_PER_PROJECT = 10;
|
|
6
|
+
|
|
7
|
+
function appendOutcomeHeader<T>(lines: string[], entry: CrossWorkspaceOutcome<T>): boolean {
|
|
8
|
+
const { outcome } = entry;
|
|
9
|
+
switch (outcome.status) {
|
|
10
|
+
case "ready":
|
|
11
|
+
lines.push(`${entry.directory} -- ready`);
|
|
12
|
+
break;
|
|
13
|
+
case "loading":
|
|
14
|
+
lines.push(`${entry.directory} -- loading: ${outcome.message}`);
|
|
15
|
+
return false;
|
|
16
|
+
case "error":
|
|
17
|
+
lines.push(`${entry.directory} -- error: ${outcome.message}`);
|
|
18
|
+
return false;
|
|
19
|
+
default: {
|
|
20
|
+
const exhaustive: never = outcome;
|
|
21
|
+
throw new Error(`unhandled workspace query outcome: ${JSON.stringify(exhaustive)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (entry.collapsedWith.length > 0) lines.push(` same workspace as: ${entry.collapsedWith.join(", ")}`);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Formats cross-project symbol outcomes with concrete, independently bounded results for model consumption. */
|
|
29
|
+
export function formatFindSymbolsAcrossProjectsModelContent(
|
|
30
|
+
results: readonly CrossWorkspaceOutcome<SymbolSearchResult>[],
|
|
31
|
+
maxBytes = DEFAULT_MODEL_CONTENT_BYTES,
|
|
32
|
+
): string {
|
|
33
|
+
const lines = ["Find Symbols Across Projects", `projects: ${results.length}`];
|
|
34
|
+
for (const entry of results) {
|
|
35
|
+
if (!appendOutcomeHeader(lines, entry)) continue;
|
|
36
|
+
if (entry.outcome.status !== "ready") continue;
|
|
37
|
+
const { result } = entry.outcome;
|
|
38
|
+
lines.push(` provenance: ${result.provenance.fidelity} via ${result.provenance.backend}`);
|
|
39
|
+
lines.push(` upstream truncated: ${result.truncated}`);
|
|
40
|
+
for (const item of result.symbols.slice(0, MAX_RESULTS_PER_PROJECT)) {
|
|
41
|
+
lines.push(` ${item.kind} ${item.name} -- ${item.location.path}:${item.location.line}:${item.location.character}`);
|
|
42
|
+
}
|
|
43
|
+
if (result.symbols.length > MAX_RESULTS_PER_PROJECT) lines.push(` ${result.symbols.length - MAX_RESULTS_PER_PROJECT} more symbols omitted`);
|
|
44
|
+
if (result.symbols.length === 0) lines.push(" no symbols matched");
|
|
45
|
+
}
|
|
46
|
+
return boundModelContentText(lines.join("\n"), maxBytes);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Formats cross-project text outcomes with concrete, independently bounded matches for model consumption. */
|
|
50
|
+
export function formatSearchTextAcrossProjectsModelContent(
|
|
51
|
+
results: readonly CrossWorkspaceOutcome<TextSearchResult>[],
|
|
52
|
+
maxBytes = DEFAULT_MODEL_CONTENT_BYTES,
|
|
53
|
+
): string {
|
|
54
|
+
const lines = ["Search Code Across Projects", `projects: ${results.length}`];
|
|
55
|
+
for (const entry of results) {
|
|
56
|
+
if (!appendOutcomeHeader(lines, entry)) continue;
|
|
57
|
+
if (entry.outcome.status !== "ready") continue;
|
|
58
|
+
const { result } = entry.outcome;
|
|
59
|
+
if (result.provenance) lines.push(` provenance: lexical via ${result.provenance.backend} (${result.provenance.indexState})`);
|
|
60
|
+
lines.push(` upstream truncated: ${result.truncated}`);
|
|
61
|
+
for (const item of result.matches.slice(0, MAX_RESULTS_PER_PROJECT)) {
|
|
62
|
+
const line = item.line.replace(/\n$/, "");
|
|
63
|
+
lines.push(` ${item.path}:${item.lineNumber}: ${line}${item.lineTruncated ? " (line truncated)" : ""}`);
|
|
64
|
+
}
|
|
65
|
+
if (result.matches.length > MAX_RESULTS_PER_PROJECT) lines.push(` ${result.matches.length - MAX_RESULTS_PER_PROJECT} more matches omitted`);
|
|
66
|
+
if (result.matches.length === 0) lines.push(" no matches");
|
|
67
|
+
}
|
|
68
|
+
return boundModelContentText(lines.join("\n"), maxBytes);
|
|
69
|
+
}
|
package/extension/src/index.ts
CHANGED
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
formatWorkspaceMapCall,
|
|
83
83
|
formatWorkspaceMapResult,
|
|
84
84
|
} from "./code-intelligence/rendering.ts";
|
|
85
|
+
import { formatFindSymbolsAcrossProjectsModelContent, formatSearchTextAcrossProjectsModelContent } from "./cross-workspace-search/model-content.ts";
|
|
85
86
|
import { type CrossWorkspaceOutcome, createLectorCrossWorkspaceSearchOperations } from "./cross-workspace-search/operations.ts";
|
|
86
87
|
import { formatCrossWorkspaceCall, formatFindSymbolsAcrossProjectsResult, formatSearchTextAcrossProjectsResult } from "./cross-workspace-search/rendering.ts";
|
|
87
88
|
import { createLectorEditOperations } from "./edit/operations.ts";
|
|
@@ -125,6 +126,11 @@ import { withLectorPresentation } from "./presentation/presentation-contract.ts"
|
|
|
125
126
|
import { presentationTitle } from "./presentation/tool-presentation.ts";
|
|
126
127
|
import { createLectorReadOperations } from "./read/operations.ts";
|
|
127
128
|
import { createReferenceBasedRenameOperations } from "./reference-based-rename/operations.ts";
|
|
129
|
+
import {
|
|
130
|
+
formatReferenceBasedRenameModelContent,
|
|
131
|
+
formatReferenceBasedRenameResult,
|
|
132
|
+
type ReferenceBasedRenameOutcome,
|
|
133
|
+
} from "./reference-based-rename/rendering.ts";
|
|
128
134
|
import { createRenameOperations } from "./rename/operations.ts";
|
|
129
135
|
import { createRepoCacheEvictOperations } from "./repo-cache/evict-operations.ts";
|
|
130
136
|
import { createRepoCacheListOperations } from "./repo-cache/list-operations.ts";
|
|
@@ -154,7 +160,13 @@ import {
|
|
|
154
160
|
monitorWorkspaceCache,
|
|
155
161
|
waitForJobCompletion,
|
|
156
162
|
} from "./workspace-cache/operations.ts";
|
|
157
|
-
import {
|
|
163
|
+
import {
|
|
164
|
+
formatJobSnapshotResult,
|
|
165
|
+
formatWorkspaceCacheCall,
|
|
166
|
+
formatWorkspaceCacheStatusResult,
|
|
167
|
+
formatWorkspaceReleaseModelContent,
|
|
168
|
+
formatWorkspaceReleaseResult,
|
|
169
|
+
} from "./workspace-cache/rendering.ts";
|
|
158
170
|
import { createLectorWriteOperations } from "./write/operations.ts";
|
|
159
171
|
|
|
160
172
|
function describeIntelligenceSource(provenance: IntelligenceProvenance): string {
|
|
@@ -1190,14 +1202,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1190
1202
|
const fromPath = resolve(cwd, params.fromPath);
|
|
1191
1203
|
const toPath = resolve(cwd, params.toPath);
|
|
1192
1204
|
const outcome = await referenceBasedRenameOperations.rename(fromPath, toPath, params.maxFiles, params.maxSymbolsPerFile);
|
|
1193
|
-
|
|
1194
|
-
`moved to ${outcome.movedTo}`,
|
|
1195
|
-
outcome.filesUpdated.length === 0
|
|
1196
|
-
? "no other files referenced it"
|
|
1197
|
-
: `updated imports in ${outcome.filesUpdated.length} file(s): ${outcome.filesUpdated.join(", ")}`,
|
|
1198
|
-
...outcome.caveats.map((caveat) => `caveat: ${caveat}`),
|
|
1199
|
-
];
|
|
1200
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { outcome } };
|
|
1205
|
+
return { content: [{ type: "text", text: formatReferenceBasedRenameModelContent(outcome) }], details: { outcome } };
|
|
1201
1206
|
},
|
|
1202
1207
|
renderCall(args, theme, context) {
|
|
1203
1208
|
const fromPath = typeof args.fromPath === "string" ? args.fromPath : "";
|
|
@@ -1217,13 +1222,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1217
1222
|
.join("\n");
|
|
1218
1223
|
return new Text(theme.fg("error", errorText || "reference_based_rename failed"), 0, 0);
|
|
1219
1224
|
}
|
|
1220
|
-
const details = result.details as { outcome?:
|
|
1225
|
+
const details = result.details as { outcome?: ReferenceBasedRenameOutcome } | undefined;
|
|
1221
1226
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1222
|
-
text.setText(
|
|
1223
|
-
details?.outcome
|
|
1224
|
-
? `${theme.fg("success", "moved")} ${theme.fg("accent", details.outcome.movedTo)} ${theme.fg("dim", `(${details.outcome.filesUpdated.length} import(s) updated)`)}`
|
|
1225
|
-
: theme.fg("success", "rename complete"),
|
|
1226
|
-
);
|
|
1227
|
+
text.setText(details?.outcome ? formatReferenceBasedRenameResult(details.outcome, theme) : theme.fg("success", "rename complete"));
|
|
1227
1228
|
return text;
|
|
1228
1229
|
},
|
|
1229
1230
|
});
|
|
@@ -1604,25 +1605,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
1604
1605
|
});
|
|
1605
1606
|
|
|
1606
1607
|
interface WorkspaceCacheToolDetails {
|
|
1607
|
-
readonly action: "status" | "populate" | "wait" | "job_status";
|
|
1608
|
+
readonly action: "status" | "populate" | "wait" | "job_status" | "release";
|
|
1608
1609
|
readonly status?: WorkspaceCacheStatus;
|
|
1609
1610
|
readonly job?: JobSnapshot<PopulateSymbolGraphResult>;
|
|
1611
|
+
readonly release?: OperationOutputs["workspace.release"];
|
|
1610
1612
|
}
|
|
1611
1613
|
|
|
1612
1614
|
registerLectorTool({
|
|
1613
1615
|
name: "workspace_cache",
|
|
1614
1616
|
label: "Workspace Cache",
|
|
1615
1617
|
description:
|
|
1616
|
-
"Checks or drives population of the workspace's persisted symbol graph -- the store reachable_from, symbol_annotations (anchor resolution), reference_based_rename, and workspace_map all read from, separate from the live language-server index find_symbols/hover/go_to_definition use. action=status reports not-cached/caching/partial/cached for the given bounds, without starting work. action=populate requests a scan and briefly waits for fast completion; a source file changing mid-scan (e.g. a concurrent edit or rename) is retried automatically in the background for up to a minute before surfacing as a real failure, no manual re-run needed. action=wait subscribes to daemon job completion, with bounded status polling only when push delivery is unavailable. action=job_status is a point-in-time diagnostic read.",
|
|
1618
|
+
"Checks or drives population of the workspace's persisted symbol graph -- the store reachable_from, symbol_annotations (anchor resolution), reference_based_rename, and workspace_map all read from, separate from the live language-server index find_symbols/hover/go_to_definition use. action=status reports not-cached/caching/partial/cached for the given bounds, without starting work. action=populate requests a scan and briefly waits for fast completion; a source file changing mid-scan (e.g. a concurrent edit or rename) is retried automatically in the background for up to a minute before surfacing as a real failure, no manual re-run needed. action=wait subscribes to daemon job completion, with bounded status polling only when push delivery is unavailable. action=job_status is a point-in-time diagnostic read. action=release safely closes idle workspace resources and unregisters it so fetched/package source cleanup can proceed.",
|
|
1617
1619
|
promptSnippet: "Check or force-populate the workspace's persisted symbol graph",
|
|
1618
1620
|
promptGuidelines: [
|
|
1619
1621
|
"Use action=populate with a larger maxFiles/maxSymbolsPerFile before relying on reachable_from/symbol_annotations/reference_based_rename against a workspace bigger than the default 500-file auto-scan -- their own errors (empty results, UnknownAnnotationAnchor, ReferenceBasedRenameRequiresFreshGraph) usually mean the graph never reached the files you need, not that population is simply still catching up.",
|
|
1620
1622
|
"When action=populate returns a queued/running job, call action=wait once with that jobId; do not run shell sleep or manually poll job_status.",
|
|
1623
|
+
"Use action=release after finishing with a fetched or package-source workspace so package_source remove/clean and repo_cache evict can reclaim it; active leases, jobs, or watches fail closed.",
|
|
1621
1624
|
],
|
|
1622
1625
|
parameters: Type.Object({
|
|
1623
|
-
action: Type.Union([Type.Literal("status"), Type.Literal("populate"), Type.Literal("wait"), Type.Literal("job_status")]),
|
|
1626
|
+
action: Type.Union([Type.Literal("status"), Type.Literal("populate"), Type.Literal("wait"), Type.Literal("job_status"), Type.Literal("release")]),
|
|
1624
1627
|
directory: Type.Optional(
|
|
1625
|
-
Type.String({ description: "Required for action=status/populate -- absolute or cwd-relative path used to resolve the workspace" }),
|
|
1628
|
+
Type.String({ description: "Required for action=status/populate/release -- absolute or cwd-relative path used to resolve the workspace" }),
|
|
1626
1629
|
),
|
|
1627
1630
|
maxFiles: Type.Optional(
|
|
1628
1631
|
Type.Number({ description: "action=status/populate only -- defaults to 500, the same bound the automatic first-touch scan uses" }),
|
|
@@ -1638,7 +1641,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1638
1641
|
),
|
|
1639
1642
|
jobId: Type.Optional(Type.String({ description: "Required for action=wait/job_status -- a jobId returned by action=populate" })),
|
|
1640
1643
|
}),
|
|
1641
|
-
async execute(
|
|
1644
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<WorkspaceCacheToolDetails>> {
|
|
1642
1645
|
if (params.action === "job_status") {
|
|
1643
1646
|
if (!params.jobId) throw new Error("workspace_cache action=job_status requires jobId");
|
|
1644
1647
|
const job = await cacheOperations.jobStatus(params.jobId);
|
|
@@ -1674,6 +1677,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
1674
1677
|
}
|
|
1675
1678
|
if (!params.directory) throw new Error(`workspace_cache action=${params.action} requires directory`);
|
|
1676
1679
|
const directory = resolve(cwd, params.directory);
|
|
1680
|
+
if (params.action === "release") {
|
|
1681
|
+
const release = await cacheOperations.release(directory, {
|
|
1682
|
+
toolName: "workspace_cache",
|
|
1683
|
+
toolCallId,
|
|
1684
|
+
signal,
|
|
1685
|
+
context: ctx,
|
|
1686
|
+
});
|
|
1687
|
+
return {
|
|
1688
|
+
content: [{ type: "text", text: formatWorkspaceReleaseModelContent(release) }],
|
|
1689
|
+
details: { action: "release", release },
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1677
1692
|
const maxFiles = params.maxFiles ?? 500;
|
|
1678
1693
|
const maxSymbolsPerFile = params.maxSymbolsPerFile ?? 100;
|
|
1679
1694
|
if (params.action === "status") {
|
|
@@ -1690,7 +1705,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1690
1705
|
};
|
|
1691
1706
|
},
|
|
1692
1707
|
renderCall(args, theme, context) {
|
|
1693
|
-
const action =
|
|
1708
|
+
const action =
|
|
1709
|
+
args.action === "populate" || args.action === "wait" || args.action === "job_status" || args.action === "release" ? args.action : "status";
|
|
1694
1710
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1695
1711
|
text.setText(formatWorkspaceCacheCall(action, args, theme));
|
|
1696
1712
|
return text;
|
|
@@ -1706,7 +1722,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1706
1722
|
}
|
|
1707
1723
|
const details = result.details as WorkspaceCacheToolDetails | undefined;
|
|
1708
1724
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1709
|
-
text.setText(
|
|
1725
|
+
text.setText(
|
|
1726
|
+
details?.action === "status"
|
|
1727
|
+
? formatWorkspaceCacheStatusResult(details.status, theme)
|
|
1728
|
+
: details?.action === "release"
|
|
1729
|
+
? formatWorkspaceReleaseResult(details.release, theme)
|
|
1730
|
+
: formatJobSnapshotResult(details?.job, theme),
|
|
1731
|
+
);
|
|
1710
1732
|
return text;
|
|
1711
1733
|
},
|
|
1712
1734
|
});
|
|
@@ -2522,7 +2544,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2522
2544
|
const directories = params.directories.map((directory) => resolve(cwd, directory));
|
|
2523
2545
|
const results = await crossWorkspaceSearchOperations.findSymbols(params.query, directories, params.timeoutMs, params.maxResults);
|
|
2524
2546
|
return {
|
|
2525
|
-
content: [{ type: "text", text:
|
|
2547
|
+
content: [{ type: "text", text: formatFindSymbolsAcrossProjectsModelContent(results) }],
|
|
2526
2548
|
details: { results },
|
|
2527
2549
|
};
|
|
2528
2550
|
},
|
|
@@ -2564,7 +2586,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2564
2586
|
const directories = params.directories.map((directory) => resolve(cwd, directory));
|
|
2565
2587
|
const results = await crossWorkspaceSearchOperations.searchText(params.query, directories, params.maxMatches, params.maxBytes, params.timeoutMs);
|
|
2566
2588
|
return {
|
|
2567
|
-
content: [{ type: "text", text:
|
|
2589
|
+
content: [{ type: "text", text: formatSearchTextAcrossProjectsModelContent(results) }],
|
|
2568
2590
|
details: { results },
|
|
2569
2591
|
};
|
|
2570
2592
|
},
|
|
@@ -89,6 +89,7 @@ export const LECTOR_TOOL_PRESENTATION_SPECS: Readonly<Record<string, ToolPresent
|
|
|
89
89
|
populate: { title: "Populate Workspace Cache", family: "status" },
|
|
90
90
|
wait: { title: "Wait for Cache Job", family: "status" },
|
|
91
91
|
job_status: { title: "Cache Job Status", family: "status" },
|
|
92
|
+
release: { title: "Release Workspace", family: "mutation" },
|
|
92
93
|
},
|
|
93
94
|
},
|
|
94
95
|
git: {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OperationOutputs } from "@danypops/lector";
|
|
2
|
+
import type { LectorTheme } from "../lector-tui-theme.ts";
|
|
3
|
+
|
|
4
|
+
export type ReferenceBasedRenameOutcome = OperationOutputs["workspace.referenceBasedRename"];
|
|
5
|
+
|
|
6
|
+
/** Formats a successful reference-based rename with every fact required for guarded transaction revert. */
|
|
7
|
+
export function formatReferenceBasedRenameModelContent(outcome: ReferenceBasedRenameOutcome): string {
|
|
8
|
+
return [
|
|
9
|
+
`moved to ${outcome.movedTo}`,
|
|
10
|
+
outcome.filesUpdated.length === 0
|
|
11
|
+
? "no other files referenced it"
|
|
12
|
+
: `updated imports in ${outcome.filesUpdated.length} file(s): ${outcome.filesUpdated.join(", ")}`,
|
|
13
|
+
`transaction ${outcome.transactionId}`,
|
|
14
|
+
...outcome.caveats.map((caveat) => `caveat: ${caveat}`),
|
|
15
|
+
].join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Formats the compact human mutation result while preserving its reusable transaction identity. */
|
|
19
|
+
export function formatReferenceBasedRenameResult(outcome: ReferenceBasedRenameOutcome, theme: LectorTheme): string {
|
|
20
|
+
return `${theme.fg("success", "moved")} ${theme.fg("accent", outcome.movedTo)} ${theme.fg("dim", `(${outcome.filesUpdated.length} import(s) updated, transaction ${outcome.transactionId})`)}`;
|
|
21
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type CacheResultCounts,
|
|
3
3
|
type JobSnapshot,
|
|
4
|
+
type OperationOutputs,
|
|
4
5
|
type PopulateSymbolGraphResult,
|
|
5
6
|
remoteErrorIs,
|
|
6
7
|
resolveLectorDaemonConnection,
|
|
7
8
|
type WorkspaceCacheStatus,
|
|
8
9
|
} from "@danypops/lector";
|
|
9
10
|
import { connectPushChannel } from "@danypops/vehicle-client/daemon-client";
|
|
10
|
-
import { lectorClient, withWorkspace, workspaceForProjectDirectory } from "../lector-client.ts";
|
|
11
|
+
import { forgetWorkspaceId, lectorClient, withWorkspace, workspaceForProjectDirectory } from "../lector-client.ts";
|
|
12
|
+
import { invokeLectorVehicleOperation, type LectorVehicleCall } from "../vehicle-client.ts";
|
|
11
13
|
|
|
12
14
|
export interface JobWatchHandle {
|
|
13
15
|
close(): void;
|
|
@@ -25,9 +27,11 @@ export type JobWatchOutcome = { readonly status: "subscribed"; readonly handle:
|
|
|
25
27
|
* want today's exact contract.
|
|
26
28
|
*/
|
|
27
29
|
const DEFAULT_POPULATE_RETRY_TIME_BUDGET_MS = 60_000;
|
|
30
|
+
const WORKSPACE_RELEASE_PERMISSIONS = ["workspace:write"];
|
|
28
31
|
|
|
29
32
|
export interface WorkspaceCacheOperations {
|
|
30
33
|
status(directory: string, maxFiles: number, maxSymbolsPerFile: number): Promise<WorkspaceCacheStatus>;
|
|
34
|
+
release(directory: string, call: LectorVehicleCall): Promise<OperationOutputs["workspace.release"]>;
|
|
31
35
|
submit(
|
|
32
36
|
directory: string,
|
|
33
37
|
maxFiles: number,
|
|
@@ -39,6 +43,8 @@ export interface WorkspaceCacheOperations {
|
|
|
39
43
|
watchJob?(jobId: string, onJob: (job: JobSnapshot<PopulateSymbolGraphResult>) => void): Promise<JobWatchOutcome>;
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
export type WorkspaceCacheMonitorOperations = Omit<WorkspaceCacheOperations, "release">;
|
|
47
|
+
|
|
42
48
|
export function createWorkspaceCacheOperations(ownerId?: string): WorkspaceCacheOperations {
|
|
43
49
|
return {
|
|
44
50
|
status(directory, maxFiles, maxSymbolsPerFile) {
|
|
@@ -50,6 +56,21 @@ export function createWorkspaceCacheOperations(ownerId?: string): WorkspaceCache
|
|
|
50
56
|
},
|
|
51
57
|
);
|
|
52
58
|
},
|
|
59
|
+
release(directory, call) {
|
|
60
|
+
return withWorkspace(
|
|
61
|
+
() => workspaceForProjectDirectory(directory),
|
|
62
|
+
async ({ workspaceId, root }) => {
|
|
63
|
+
const result = await invokeLectorVehicleOperation<OperationOutputs["workspace.release"]>(
|
|
64
|
+
"workspace.release",
|
|
65
|
+
{ workspaceId },
|
|
66
|
+
WORKSPACE_RELEASE_PERMISSIONS,
|
|
67
|
+
call,
|
|
68
|
+
);
|
|
69
|
+
forgetWorkspaceId(root);
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
},
|
|
53
74
|
submit(directory, maxFiles, maxSymbolsPerFile, waitMs = 0, retryTimeBudgetMs = DEFAULT_POPULATE_RETRY_TIME_BUDGET_MS) {
|
|
54
75
|
return withWorkspace(
|
|
55
76
|
() => workspaceForProjectDirectory(directory),
|
|
@@ -159,7 +180,7 @@ export type JobCompletionOutcome =
|
|
|
159
180
|
|
|
160
181
|
/** Waits on Vehicle push delivery and checks status on a bounded cadence when push is unavailable or disconnected. */
|
|
161
182
|
export async function waitForJobCompletion(
|
|
162
|
-
operations:
|
|
183
|
+
operations: WorkspaceCacheMonitorOperations,
|
|
163
184
|
jobId: string,
|
|
164
185
|
options: WaitForJobCompletionOptions,
|
|
165
186
|
): Promise<JobCompletionOutcome> {
|
|
@@ -205,7 +226,7 @@ export async function waitForJobCompletion(
|
|
|
205
226
|
}
|
|
206
227
|
|
|
207
228
|
/** Drives one bounded session cache lifecycle; Pi event handlers only render its states. */
|
|
208
|
-
export async function monitorWorkspaceCache(operations:
|
|
229
|
+
export async function monitorWorkspaceCache(operations: WorkspaceCacheMonitorOperations, options: MonitorWorkspaceCacheOptions): Promise<void> {
|
|
209
230
|
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
210
231
|
const initial = await operations.status(options.directory, options.maxFiles, options.maxSymbolsPerFile);
|
|
211
232
|
if (!options.shouldContinue()) return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { CacheResultCounts, JobSnapshot, PopulateSymbolGraphResult, WorkspaceCacheStatus } from "@danypops/lector";
|
|
1
|
+
import type { CacheResultCounts, JobSnapshot, OperationOutputs, PopulateSymbolGraphResult, WorkspaceCacheStatus } from "@danypops/lector";
|
|
2
2
|
import type { LectorTheme } from "../lector-tui-theme.ts";
|
|
3
3
|
import { presentationTitle } from "../presentation/tool-presentation.ts";
|
|
4
4
|
|
|
5
|
-
type WorkspaceCacheAction = "status" | "populate" | "wait" | "job_status";
|
|
5
|
+
type WorkspaceCacheAction = "status" | "populate" | "wait" | "job_status" | "release";
|
|
6
6
|
|
|
7
7
|
export function formatWorkspaceCacheCall(
|
|
8
8
|
action: WorkspaceCacheAction,
|
|
@@ -29,6 +29,23 @@ function formatResultCounts(result: CacheResultCounts): string {
|
|
|
29
29
|
return `${result.filesProcessed}/${result.filesAttempted} files${failed}, ${result.symbolsProcessed} symbols, ${result.nodesAdded} nodes, ${result.edgesAdded} edges`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function formatWorkspaceReleaseModelContent(outcome: OperationOutputs["workspace.release"]): string {
|
|
33
|
+
return [
|
|
34
|
+
`released workspace ${outcome.workspaceId}`,
|
|
35
|
+
`closed indexes: ${outcome.closedIndexes}`,
|
|
36
|
+
`closed graph: ${outcome.closedGraph}`,
|
|
37
|
+
`closed watch: ${outcome.closedWatch}`,
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatWorkspaceReleaseResult(outcome: OperationOutputs["workspace.release"] | undefined, theme: LectorTheme): string {
|
|
42
|
+
if (!outcome) return theme.fg("dim", "No result.");
|
|
43
|
+
return theme.fg(
|
|
44
|
+
"success",
|
|
45
|
+
`released ${outcome.workspaceId} -- ${outcome.closedIndexes} index(es), graph ${outcome.closedGraph ? "closed" : "idle"}, watch ${outcome.closedWatch ? "closed" : "idle"}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
export function formatWorkspaceCacheStatusResult(status: WorkspaceCacheStatus | undefined, theme: LectorTheme): string {
|
|
33
50
|
if (!status) return theme.fg("dim", "No result.");
|
|
34
51
|
if (status.status === "not-cached") return theme.fg("warning", `not cached (${status.reason})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Pi host adapter for Lector: overrides read/write/edit with a daemon-backed, hash-guarded filesystem",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@danypops/vehicle-client-pi": "^0.45.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@danypops/lector": "^0.
|
|
25
|
+
"@danypops/lector": "^0.21.0",
|
|
26
26
|
"@danypops/vehicle-client": "^0.10.8",
|
|
27
27
|
"@danypops/vehicle-core": "^0.19.1",
|
|
28
28
|
"malevich-tui-components": "^0.32.1",
|