@gajae-code/coding-agent 0.11.4 → 0.11.6
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/CHANGELOG.md +23 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +31 -1
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
- package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/owner.d.ts +3 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -0
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-transcript-format.d.ts +58 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
- package/dist/types/plan-mode/approved-plan.d.ts +1 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +42 -4
- package/dist/types/sdk/bus/config-commands.d.ts +8 -0
- package/dist/types/sdk/bus/config.d.ts +22 -0
- package/dist/types/sdk/bus/index.d.ts +162 -2
- package/dist/types/sdk/bus/notification-service.d.ts +71 -8
- package/dist/types/sdk/bus/session-control.d.ts +11 -8
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
- package/dist/types/sdk/session.d.ts +16 -1
- package/dist/types/secrets/index.d.ts +4 -2
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +31 -2
- package/dist/types/session/artifacts.d.ts +30 -8
- package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
- package/dist/types/session/session-manager.d.ts +98 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/session/yield-queue.d.ts +8 -0
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/tool-discovery/tool-index.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +48 -0
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/commands/sdk.ts +7 -2
- package/src/config/settings-schema.ts +31 -1
- package/src/daemon/runtime.ts +2 -2
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
- package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
- package/src/defaults/gjc/skills/team/SKILL.md +7 -9
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
- package/src/eval/py/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
- package/src/gjc-runtime/deep-interview-state.ts +127 -3
- package/src/gjc-runtime/launch-tmux.ts +61 -6
- package/src/gjc-runtime/managed-owner-admission.ts +352 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/gjc-runtime/state-runtime.ts +38 -12
- package/src/gjc-runtime/team-runtime.ts +1 -1
- package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
- package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
- package/src/harness-control-plane/owner.ts +59 -3
- package/src/hindsight/client.ts +65 -2
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +36 -12
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -24
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +321 -0
- package/src/modes/components/transcript-viewer-overlay.ts +39 -17
- package/src/modes/controllers/command-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -2
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- package/src/modes/controllers/plan-mode-controller.ts +2 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +70 -8
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/runtime-init.ts +16 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/lsp.md +3 -3
- package/src/runtime-mcp/manager.ts +16 -1
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/sdk/broker/session-index.ts +122 -64
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +538 -130
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/config-commands.ts +22 -0
- package/src/sdk/bus/config.ts +37 -0
- package/src/sdk/bus/index.ts +388 -94
- package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
- package/src/sdk/bus/notification-service.ts +284 -62
- package/src/sdk/bus/recent-activity.ts +14 -5
- package/src/sdk/bus/session-control.ts +150 -44
- package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -3
- package/src/sdk/bus/telegram-daemon-control.ts +312 -76
- package/src/sdk/bus/telegram-daemon.ts +1916 -285
- package/src/sdk/host/control/dispatch.ts +14 -5
- package/src/sdk/protocol/operation-inventory.generated.json +29 -0
- package/src/sdk/session.ts +155 -27
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +1447 -852
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +200 -38
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1121 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/session/yield-queue.ts +20 -0
- package/src/slash-commands/builtin-registry.ts +36 -0
- package/src/task/executor.ts +71 -26
- package/src/task/index.ts +63 -20
- package/src/tool-discovery/tool-index.ts +18 -0
- package/src/tools/ask-answer-registry.ts +15 -1
- package/src/tools/ask.ts +129 -32
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/index.ts +4 -0
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
|
@@ -1,14 +1,35 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import * as fsSync from "node:fs";
|
|
1
3
|
import * as fs from "node:fs/promises";
|
|
2
4
|
import * as os from "node:os";
|
|
3
5
|
import * as path from "node:path";
|
|
6
|
+
import { exactRemoveDirectoryTree, type NativeDirectoryTreeSnapshot, snapshotDirectoryTree } from "@gajae-code/natives";
|
|
4
7
|
import { isEnoent } from "@gajae-code/utils";
|
|
5
8
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
6
9
|
import { parseInternalUrl } from "./parse";
|
|
7
10
|
import { validateRelativePath } from "./skill-protocol";
|
|
8
11
|
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
9
12
|
|
|
13
|
+
export interface ManagedLegacyLocalMigrationEntry {
|
|
14
|
+
readonly relativePath: string;
|
|
15
|
+
readonly kind: "directory" | "file";
|
|
16
|
+
readonly bytes?: Uint8Array;
|
|
17
|
+
readonly sha256?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Opaque authority for a managed session's legacy `local` artifact tree. */
|
|
21
|
+
export interface ManagedLegacyLocalMigrationSource {
|
|
22
|
+
capture(): Promise<{
|
|
23
|
+
readonly snapshot: NativeDirectoryTreeSnapshot;
|
|
24
|
+
readonly entries: readonly ManagedLegacyLocalMigrationEntry[];
|
|
25
|
+
} | null>;
|
|
26
|
+
retire(snapshot: NativeDirectoryTreeSnapshot): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
export interface LocalProtocolOptions {
|
|
11
30
|
getArtifactsDir?: () => string | null;
|
|
31
|
+
isManagedDestination?: () => boolean;
|
|
32
|
+
getManagedLegacyLocalMigrationSource?: () => ManagedLegacyLocalMigrationSource | null;
|
|
12
33
|
getSessionId?: () => string | null;
|
|
13
34
|
}
|
|
14
35
|
|
|
@@ -45,6 +66,7 @@ async function listFilesRecursively(rootPath: string): Promise<string[]> {
|
|
|
45
66
|
const entries = await fs.readdir(absoluteDir, { withFileTypes: true });
|
|
46
67
|
|
|
47
68
|
for (const entry of entries) {
|
|
69
|
+
if (entry.name === LEGACY_MIGRATION_MARKER) continue;
|
|
48
70
|
const entryPath = path.join(relativeDir, entry.name);
|
|
49
71
|
if (entry.isDirectory()) {
|
|
50
72
|
pending.push(entryPath);
|
|
@@ -108,20 +130,404 @@ function extractRelativePath(url: InternalUrl): string {
|
|
|
108
130
|
return decoded;
|
|
109
131
|
}
|
|
110
132
|
|
|
111
|
-
|
|
133
|
+
function safeSessionId(options: LocalProtocolOptions): string {
|
|
134
|
+
const sessionId = (options.getSessionId?.() ?? "session").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
135
|
+
return sessionId === "" || sessionId === "." || sessionId === ".." ? "session" : sessionId;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function assertDirectoryNotSymlink(directoryPath: string): Promise<void> {
|
|
139
|
+
const stat = await fs.lstat(directoryPath);
|
|
140
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
141
|
+
throw new Error("Unsafe local:// root");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const LEGACY_MIGRATION_MARKER = ".gjc-local-legacy-migrated-v1";
|
|
146
|
+
const MAX_LEGACY_LOCAL_BYTES = 64 * 1024 * 1024;
|
|
147
|
+
|
|
148
|
+
interface LegacyEntrySnapshot {
|
|
149
|
+
readonly relativePath: string;
|
|
150
|
+
readonly dev: bigint;
|
|
151
|
+
readonly ino: bigint;
|
|
152
|
+
readonly size: bigint;
|
|
153
|
+
readonly digest?: string;
|
|
154
|
+
readonly bytes?: Buffer;
|
|
155
|
+
}
|
|
156
|
+
interface LegacyIdentityStat {
|
|
157
|
+
readonly dev: bigint;
|
|
158
|
+
readonly ino: bigint;
|
|
159
|
+
readonly size: bigint;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function matchesSnapshot(stat: LegacyIdentityStat, snapshot: LegacyEntrySnapshot): boolean {
|
|
163
|
+
return stat.dev === snapshot.dev && stat.ino === snapshot.ino && stat.size === snapshot.size;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function snapshotDirectory(directoryPath: string, relativePath: string): Promise<LegacyEntrySnapshot> {
|
|
167
|
+
const stat = await fs.lstat(directoryPath, { bigint: true });
|
|
168
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("Unsafe legacy local:// migration source");
|
|
169
|
+
return { relativePath, dev: stat.dev, ino: stat.ino, size: stat.size };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function snapshotRegularFile(filePath: string, relativePath: string): Promise<LegacyEntrySnapshot> {
|
|
173
|
+
const before = await fs.lstat(filePath, { bigint: true });
|
|
174
|
+
if (!before.isFile() || before.isSymbolicLink()) throw new Error("Unsafe legacy local:// migration source");
|
|
175
|
+
const flags = fsSync.constants.O_RDONLY | (process.platform === "win32" ? 0 : (fsSync.constants.O_NOFOLLOW ?? 0));
|
|
176
|
+
const handle = await fs.open(filePath, flags);
|
|
177
|
+
try {
|
|
178
|
+
const opened = await handle.stat({ bigint: true });
|
|
179
|
+
const identity = { relativePath, dev: before.dev, ino: before.ino, size: before.size };
|
|
180
|
+
if (!opened.isFile() || !matchesSnapshot(opened, identity))
|
|
181
|
+
throw new Error("Legacy local:// migration source changed during capture");
|
|
182
|
+
const bytes = await handle.readFile();
|
|
183
|
+
const after = await fs.lstat(filePath, { bigint: true });
|
|
184
|
+
if (!matchesSnapshot(after, identity)) throw new Error("Legacy local:// migration source changed during capture");
|
|
185
|
+
return { ...identity, digest: createHash("sha256").update(bytes).digest("hex"), bytes };
|
|
186
|
+
} finally {
|
|
187
|
+
await handle.close();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function manifestsMatch(left: readonly LegacyEntrySnapshot[], right: readonly LegacyEntrySnapshot[]): boolean {
|
|
192
|
+
return (
|
|
193
|
+
left.length === right.length &&
|
|
194
|
+
left.every(
|
|
195
|
+
(entry, index) =>
|
|
196
|
+
entry.relativePath === right[index]?.relativePath &&
|
|
197
|
+
entry.dev === right[index]?.dev &&
|
|
198
|
+
entry.ino === right[index]?.ino &&
|
|
199
|
+
entry.size === right[index]?.size &&
|
|
200
|
+
entry.digest === right[index]?.digest,
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function captureLegacyManifest(root: string): Promise<readonly LegacyEntrySnapshot[]> {
|
|
206
|
+
const manifest: LegacyEntrySnapshot[] = [];
|
|
207
|
+
let copiedBytes = 0n;
|
|
208
|
+
const captureDirectory = async (directoryPath: string, relativePath: string): Promise<void> => {
|
|
209
|
+
const directory = await snapshotDirectory(directoryPath, relativePath);
|
|
210
|
+
manifest.push(directory);
|
|
211
|
+
const entries = (await fs.readdir(directoryPath, { withFileTypes: true })).sort((a, b) =>
|
|
212
|
+
a.name.localeCompare(b.name),
|
|
213
|
+
);
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const entryPath = path.join(directoryPath, entry.name);
|
|
216
|
+
const entryRelativePath = path.join(relativePath, entry.name);
|
|
217
|
+
if (entry.name === LEGACY_MIGRATION_MARKER) throw new Error("Unsafe legacy local:// migration source");
|
|
218
|
+
if (entry.isDirectory()) await captureDirectory(entryPath, entryRelativePath);
|
|
219
|
+
else {
|
|
220
|
+
const file = await snapshotRegularFile(entryPath, entryRelativePath);
|
|
221
|
+
copiedBytes += file.size;
|
|
222
|
+
if (copiedBytes > BigInt(MAX_LEGACY_LOCAL_BYTES))
|
|
223
|
+
throw new Error("Legacy local:// migration exceeds the safe size limit");
|
|
224
|
+
manifest.push(file);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const after = await fs.lstat(directoryPath, { bigint: true });
|
|
228
|
+
if (!matchesSnapshot(after, directory))
|
|
229
|
+
throw new Error("Legacy local:// migration source changed during capture");
|
|
230
|
+
};
|
|
231
|
+
await captureDirectory(root, "");
|
|
232
|
+
return manifest;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function copyLegacyManifest(
|
|
236
|
+
source: string,
|
|
237
|
+
destination: string,
|
|
238
|
+
manifest: readonly LegacyEntrySnapshot[],
|
|
239
|
+
): Promise<void> {
|
|
240
|
+
for (const entry of manifest) {
|
|
241
|
+
if (entry.relativePath === "") {
|
|
242
|
+
await fs.mkdir(destination, { mode: 0o700 });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const from = path.join(source, entry.relativePath);
|
|
246
|
+
const to = path.join(destination, entry.relativePath);
|
|
247
|
+
if (entry.digest === undefined) {
|
|
248
|
+
await fs.mkdir(to, { mode: 0o700 });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const captured = await snapshotRegularFile(from, entry.relativePath);
|
|
252
|
+
if (captured.digest !== entry.digest || !matchesSnapshot(captured, entry))
|
|
253
|
+
throw new Error("Legacy local:// migration source changed during capture");
|
|
254
|
+
if (entry.bytes === undefined) throw new Error("Legacy local:// migration snapshot is incomplete");
|
|
255
|
+
await fs.writeFile(to, entry.bytes, { mode: 0o600, flag: "wx" });
|
|
256
|
+
const copied = await fs.readFile(to);
|
|
257
|
+
if (createHash("sha256").update(copied).digest("hex") !== entry.digest)
|
|
258
|
+
throw new Error("Legacy local:// migration destination verification failed");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function retireLegacyTree(legacyRoot: string, manifest: readonly LegacyEntrySnapshot[]): Promise<void> {
|
|
263
|
+
const root = manifest[0];
|
|
264
|
+
if (root?.relativePath !== "") throw new Error("Legacy local:// migration manifest has no root");
|
|
265
|
+
const before = await fs.lstat(legacyRoot, { bigint: true });
|
|
266
|
+
if (!matchesSnapshot(before, root)) throw new Error("Legacy local:// migration source changed during retirement");
|
|
267
|
+
const captured = snapshotDirectoryTree(legacyRoot);
|
|
268
|
+
if (!captured.ok || !captured.snapshot) {
|
|
269
|
+
throw new Error(`Legacy local:// migration retirement snapshot failed: ${captured.code ?? "unknown"}`);
|
|
270
|
+
}
|
|
271
|
+
if (captured.snapshot.entries.length !== manifest.length) {
|
|
272
|
+
throw new Error("Legacy local:// migration retirement manifest changed");
|
|
273
|
+
}
|
|
274
|
+
const expected = new Map(manifest.map(entry => [entry.relativePath.replaceAll(path.sep, "/"), entry]));
|
|
275
|
+
for (const entry of captured.snapshot.entries) {
|
|
276
|
+
const original = expected.get(entry.relativePath);
|
|
277
|
+
if (
|
|
278
|
+
!original ||
|
|
279
|
+
entry.dev !== original.dev.toString() ||
|
|
280
|
+
entry.ino !== original.ino.toString() ||
|
|
281
|
+
entry.size !== original.size.toString() ||
|
|
282
|
+
(entry.kind === "file" ? entry.sha256 !== original.digest : original.digest !== undefined)
|
|
283
|
+
) {
|
|
284
|
+
throw new Error("Legacy local:// migration retirement authority differs from copied manifest");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const removed = exactRemoveDirectoryTree(legacyRoot, captured.snapshot);
|
|
288
|
+
if (!removed.ok) throw new Error(`Legacy local:// migration retirement failed: ${removed.code}`);
|
|
289
|
+
}
|
|
290
|
+
async function migrateManagedLegacyLocal(
|
|
291
|
+
source: ManagedLegacyLocalMigrationSource,
|
|
292
|
+
localRoot: string,
|
|
293
|
+
scratchParent: string,
|
|
294
|
+
marker: string,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
const captured = await source.capture();
|
|
297
|
+
if (!captured) {
|
|
298
|
+
await fs.writeFile(marker, "absent\n", { mode: 0o600, flag: "wx" });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
let copiedBytes = 0;
|
|
302
|
+
for (const entry of captured.entries) {
|
|
303
|
+
if (entry.kind === "file") {
|
|
304
|
+
if (!entry.bytes || !entry.sha256) throw new Error("Legacy local:// migration snapshot is incomplete");
|
|
305
|
+
copiedBytes += entry.bytes.byteLength;
|
|
306
|
+
if (copiedBytes > MAX_LEGACY_LOCAL_BYTES)
|
|
307
|
+
throw new Error("Legacy local:// migration exceeds the safe size limit");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const staging = path.join(scratchParent, `.gjc-local-migration-${randomUUID()}`);
|
|
311
|
+
const installed: Array<{ readonly path: string; readonly dev: bigint; readonly ino: bigint }> = [];
|
|
312
|
+
let installedMarker: { readonly dev: bigint; readonly ino: bigint } | null = null;
|
|
313
|
+
try {
|
|
314
|
+
await fs.mkdir(staging, { mode: 0o700 });
|
|
315
|
+
for (const entry of captured.entries) {
|
|
316
|
+
if (entry.relativePath === "") continue;
|
|
317
|
+
const target = path.join(staging, entry.relativePath);
|
|
318
|
+
if (entry.kind === "directory") await fs.mkdir(target, { mode: 0o700 });
|
|
319
|
+
else {
|
|
320
|
+
await fs.writeFile(target, entry.bytes!, { mode: 0o600, flag: "wx" });
|
|
321
|
+
const copied = await fs.readFile(target);
|
|
322
|
+
if (createHash("sha256").update(copied).digest("hex") !== entry.sha256)
|
|
323
|
+
throw new Error("Legacy local:// migration destination verification failed");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (const entry of await fs.readdir(staging)) {
|
|
327
|
+
const destination = path.join(localRoot, entry);
|
|
328
|
+
try {
|
|
329
|
+
await fs.lstat(destination);
|
|
330
|
+
throw new Error("Legacy local:// migration destination is ambiguous");
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (!isEnoent(error)) throw error;
|
|
333
|
+
}
|
|
334
|
+
await fs.rename(path.join(staging, entry), destination);
|
|
335
|
+
const identity = await fs.lstat(destination, { bigint: true });
|
|
336
|
+
installed.push({ path: destination, dev: identity.dev, ino: identity.ino });
|
|
337
|
+
}
|
|
338
|
+
await fs.writeFile(marker, "verified\n", { mode: 0o600, flag: "wx" });
|
|
339
|
+
const markerIdentity = await fs.lstat(marker, { bigint: true });
|
|
340
|
+
installedMarker = { dev: markerIdentity.dev, ino: markerIdentity.ino };
|
|
341
|
+
source.retire(captured.snapshot);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const currentMarker = await fs.lstat(marker, { bigint: true }).catch(() => null);
|
|
344
|
+
if (installedMarker && currentMarker?.dev === installedMarker.dev && currentMarker.ino === installedMarker.ino)
|
|
345
|
+
await fs.rm(marker, { force: true });
|
|
346
|
+
for (const destination of installed.reverse()) {
|
|
347
|
+
const current = await fs.lstat(destination.path, { bigint: true }).catch(() => null);
|
|
348
|
+
if (current?.dev === destination.dev && current.ino === destination.ino)
|
|
349
|
+
await fs.rm(destination.path, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
throw error;
|
|
352
|
+
} finally {
|
|
353
|
+
await fs.rm(staging, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function readMigrationMarker(marker: string): Promise<boolean> {
|
|
358
|
+
try {
|
|
359
|
+
const snapshot = await snapshotRegularFile(marker, LEGACY_MIGRATION_MARKER);
|
|
360
|
+
if (snapshot.size > 32n || snapshot.bytes === undefined) throw new Error("Unsafe local:// migration marker");
|
|
361
|
+
const value = snapshot.bytes.toString("utf8");
|
|
362
|
+
if (value !== "verified\n" && value !== "absent\n") throw new Error("Unsafe local:// migration marker");
|
|
363
|
+
return true;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (isEnoent(error)) return false;
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function migrateLegacyLocal(
|
|
371
|
+
options: LocalProtocolOptions,
|
|
372
|
+
localRoot: string,
|
|
373
|
+
scratchParent: string,
|
|
374
|
+
): Promise<void> {
|
|
375
|
+
const marker = path.join(localRoot, LEGACY_MIGRATION_MARKER);
|
|
376
|
+
if (await readMigrationMarker(marker)) return;
|
|
377
|
+
const managedSource = options.getManagedLegacyLocalMigrationSource?.();
|
|
378
|
+
if (managedSource) {
|
|
379
|
+
await migrateManagedLegacyLocal(managedSource, localRoot, scratchParent, marker);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
112
382
|
const artifactsDir = options.getArtifactsDir?.();
|
|
113
|
-
if (artifactsDir) {
|
|
114
|
-
|
|
383
|
+
if (!artifactsDir) {
|
|
384
|
+
await fs.writeFile(marker, "absent\n", { mode: 0o600, flag: "wx" });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const legacyRoot = path.resolve(artifactsDir, "local");
|
|
388
|
+
let manifest: readonly LegacyEntrySnapshot[];
|
|
389
|
+
try {
|
|
390
|
+
manifest = await captureLegacyManifest(legacyRoot);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (isEnoent(error)) {
|
|
393
|
+
await fs.writeFile(marker, "absent\n", { mode: 0o600, flag: "wx" });
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
115
397
|
}
|
|
398
|
+
const staging = path.join(scratchParent, `.gjc-local-migration-${randomUUID()}`);
|
|
399
|
+
try {
|
|
400
|
+
await copyLegacyManifest(legacyRoot, staging, manifest);
|
|
401
|
+
const verifiedManifest = await captureLegacyManifest(legacyRoot);
|
|
402
|
+
if (!manifestsMatch(verifiedManifest, manifest))
|
|
403
|
+
throw new Error("Legacy local:// migration source changed during capture");
|
|
404
|
+
for (const entry of await fs.readdir(staging)) {
|
|
405
|
+
try {
|
|
406
|
+
await fs.lstat(path.join(localRoot, entry));
|
|
407
|
+
throw new Error("Legacy local:// migration destination is ambiguous");
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (!isEnoent(error)) throw error;
|
|
410
|
+
}
|
|
411
|
+
await fs.rename(path.join(staging, entry), path.join(localRoot, entry));
|
|
412
|
+
}
|
|
413
|
+
await retireLegacyTree(legacyRoot, manifest);
|
|
414
|
+
await fs.writeFile(marker, "verified\n", { mode: 0o600, flag: "wx" });
|
|
415
|
+
} finally {
|
|
416
|
+
await fs.rm(staging, { recursive: true, force: true });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const initializedLocalRoots = new Set<string>();
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Completes the mandatory legacy migration gate for a local root.
|
|
424
|
+
* Call and await this before using the synchronous path resolver for reads or writes.
|
|
425
|
+
*/
|
|
426
|
+
function explicitLocalRoot(options: LocalProtocolOptions): string | null {
|
|
427
|
+
if (options.isManagedDestination?.()) return null;
|
|
428
|
+
const artifactsDir = options.getArtifactsDir?.();
|
|
429
|
+
return artifactsDir ? path.resolve(artifactsDir, "local") : null;
|
|
430
|
+
}
|
|
116
431
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
432
|
+
async function initializeExplicitLocalRoot(localRoot: string): Promise<string> {
|
|
433
|
+
await fs.mkdir(path.dirname(localRoot), { recursive: true });
|
|
434
|
+
await assertDirectoryNotSymlink(path.dirname(localRoot));
|
|
435
|
+
await fs.mkdir(localRoot, { recursive: true });
|
|
436
|
+
await assertDirectoryNotSymlink(localRoot);
|
|
437
|
+
return await fs.realpath(localRoot);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function initializeExplicitLocalRootSync(localRoot: string): string {
|
|
441
|
+
fsSync.mkdirSync(path.dirname(localRoot), { recursive: true });
|
|
442
|
+
const parentStat = fsSync.lstatSync(path.dirname(localRoot));
|
|
443
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) throw new Error("Unsafe local:// root");
|
|
444
|
+
fsSync.mkdirSync(localRoot, { recursive: true });
|
|
445
|
+
const rootStat = fsSync.lstatSync(localRoot);
|
|
446
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Unsafe local:// root");
|
|
447
|
+
return fsSync.realpathSync(localRoot);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export async function initializeLocalRoot(options: LocalProtocolOptions): Promise<string> {
|
|
451
|
+
const explicitRoot = explicitLocalRoot(options);
|
|
452
|
+
if (explicitRoot) return await initializeExplicitLocalRoot(explicitRoot);
|
|
453
|
+
const localRoot = path.resolve(resolveLocalRoot(options));
|
|
454
|
+
const scratchParent = path.dirname(localRoot);
|
|
455
|
+
|
|
456
|
+
await fs.mkdir(scratchParent, { recursive: true, mode: 0o700 });
|
|
457
|
+
await assertDirectoryNotSymlink(scratchParent);
|
|
458
|
+
try {
|
|
459
|
+
await fs.mkdir(localRoot, { mode: 0o700 });
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
462
|
+
await assertDirectoryNotSymlink(localRoot);
|
|
463
|
+
} else {
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
await assertDirectoryNotSymlink(localRoot);
|
|
468
|
+
await migrateLegacyLocal(options, localRoot, scratchParent);
|
|
469
|
+
|
|
470
|
+
const resolvedScratchParent = await fs.realpath(scratchParent);
|
|
471
|
+
const resolvedLocalRoot = await fs.realpath(localRoot);
|
|
472
|
+
ensureWithinRoot(resolvedLocalRoot, resolvedScratchParent);
|
|
473
|
+
if (resolvedLocalRoot === resolvedScratchParent) {
|
|
474
|
+
throw new Error("Unsafe local:// root");
|
|
475
|
+
}
|
|
476
|
+
initializedLocalRoots.add(resolvedLocalRoot);
|
|
477
|
+
return resolvedLocalRoot;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function initializeLocalRootSyncWhenLegacyAbsent(options: LocalProtocolOptions, localRoot: string): void {
|
|
481
|
+
const explicitRoot = explicitLocalRoot(options);
|
|
482
|
+
if (explicitRoot) {
|
|
483
|
+
initializeExplicitLocalRootSync(explicitRoot);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (initializedLocalRoots.has(localRoot)) return;
|
|
487
|
+
const scratchParent = path.dirname(localRoot);
|
|
488
|
+
fsSync.mkdirSync(scratchParent, { recursive: true, mode: 0o700 });
|
|
489
|
+
const parentStat = fsSync.lstatSync(scratchParent);
|
|
490
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) throw new Error("Unsafe local:// root");
|
|
491
|
+
try {
|
|
492
|
+
fsSync.mkdirSync(localRoot, { mode: 0o700 });
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
495
|
+
}
|
|
496
|
+
const rootStat = fsSync.lstatSync(localRoot);
|
|
497
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Unsafe local:// root");
|
|
498
|
+
const marker = path.join(localRoot, LEGACY_MIGRATION_MARKER);
|
|
499
|
+
try {
|
|
500
|
+
const value = fsSync.readFileSync(marker, "utf8");
|
|
501
|
+
if (value !== "verified\n" && value !== "absent\n") throw new Error("Unsafe local:// migration marker");
|
|
502
|
+
initializedLocalRoots.add(localRoot);
|
|
503
|
+
return;
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!isEnoent(error)) throw error;
|
|
506
|
+
}
|
|
507
|
+
if (options.getManagedLegacyLocalMigrationSource?.()) {
|
|
508
|
+
throw new Error("local:// legacy migration must complete before path resolution");
|
|
509
|
+
}
|
|
510
|
+
const artifactsDir = options.getArtifactsDir?.();
|
|
511
|
+
if (artifactsDir && fsSync.existsSync(path.resolve(artifactsDir, "local"))) {
|
|
512
|
+
throw new Error("local:// legacy migration must complete before path resolution");
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
fsSync.writeFileSync(marker, "absent\n", { mode: 0o600, flag: "wx" });
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
518
|
+
if (fsSync.readFileSync(marker, "utf8") !== "absent\n") throw new Error("Unsafe local:// migration marker");
|
|
519
|
+
}
|
|
520
|
+
initializedLocalRoots.add(localRoot);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function resolveLocalRoot(options: LocalProtocolOptions): string {
|
|
524
|
+
return explicitLocalRoot(options) ?? path.join(os.tmpdir(), "gjc-local", safeSessionId(options));
|
|
120
525
|
}
|
|
121
526
|
|
|
122
527
|
export function resolveLocalUrlToPath(input: string | InternalUrl, options: LocalProtocolOptions): string {
|
|
123
528
|
const url = typeof input === "string" ? parseLocalUrl(input) : input;
|
|
124
529
|
const localRoot = path.resolve(resolveLocalRoot(options));
|
|
530
|
+
initializeLocalRootSyncWhenLegacyAbsent(options, localRoot);
|
|
125
531
|
const relativePath = extractRelativePath(url);
|
|
126
532
|
|
|
127
533
|
if (!relativePath) {
|
|
@@ -177,6 +583,7 @@ export class LocalProtocolHandler implements ProtocolHandler {
|
|
|
177
583
|
static resetOverrideForTests(): void {
|
|
178
584
|
LocalProtocolHandler.#override = undefined;
|
|
179
585
|
LocalProtocolHandler.#ownedOverrides = [];
|
|
586
|
+
initializedLocalRoots.clear();
|
|
180
587
|
}
|
|
181
588
|
|
|
182
589
|
/**
|
|
@@ -199,6 +606,8 @@ export class LocalProtocolHandler implements ProtocolHandler {
|
|
|
199
606
|
if (!sessionManager) return undefined;
|
|
200
607
|
return {
|
|
201
608
|
getArtifactsDir: () => sessionManager.getArtifactsDir(),
|
|
609
|
+
isManagedDestination: () => sessionManager.isManagedDestination(),
|
|
610
|
+
getManagedLegacyLocalMigrationSource: () => sessionManager.getManagedLegacyLocalMigrationSource(),
|
|
202
611
|
getSessionId: () => sessionManager.getSessionId(),
|
|
203
612
|
};
|
|
204
613
|
}
|
|
@@ -209,18 +618,7 @@ export class LocalProtocolHandler implements ProtocolHandler {
|
|
|
209
618
|
throw new Error("No session - local:// unavailable");
|
|
210
619
|
}
|
|
211
620
|
|
|
212
|
-
const
|
|
213
|
-
await fs.mkdir(localRoot, { recursive: true });
|
|
214
|
-
|
|
215
|
-
let resolvedRoot: string;
|
|
216
|
-
try {
|
|
217
|
-
resolvedRoot = await fs.realpath(localRoot);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (isEnoent(error)) {
|
|
220
|
-
throw new Error("Unable to initialize local:// root");
|
|
221
|
-
}
|
|
222
|
-
throw error;
|
|
223
|
-
}
|
|
621
|
+
const resolvedRoot = await initializeLocalRoot(opts);
|
|
224
622
|
|
|
225
623
|
const relativePath = extractRelativePath(url);
|
|
226
624
|
const targetPath = relativePath ? path.resolve(resolvedRoot, relativePath) : resolvedRoot;
|
package/src/lsp/index.ts
CHANGED
|
@@ -461,88 +461,6 @@ async function waitForDiagnostics(
|
|
|
461
461
|
return getAcceptedDiagnostics(client.diagnostics.get(uri), expectedDocumentVersion, allowUnversioned) ?? [];
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
/** Project type detection result */
|
|
465
|
-
interface ProjectType {
|
|
466
|
-
type: "rust" | "typescript" | "go" | "python" | "unknown";
|
|
467
|
-
command?: string[];
|
|
468
|
-
description: string;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/** Detect project type from root markers */
|
|
472
|
-
function detectProjectType(cwd: string): ProjectType {
|
|
473
|
-
// Check for Rust (Cargo.toml)
|
|
474
|
-
if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
|
|
475
|
-
return { type: "rust", command: ["cargo", "check", "--message-format=short"], description: "Rust (cargo check)" };
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Check for TypeScript (tsconfig.json)
|
|
479
|
-
if (fs.existsSync(path.join(cwd, "tsconfig.json"))) {
|
|
480
|
-
return { type: "typescript", command: ["npx", "tsc", "--noEmit"], description: "TypeScript (tsc --noEmit)" };
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Check for Go (go.mod)
|
|
484
|
-
if (fs.existsSync(path.join(cwd, "go.mod"))) {
|
|
485
|
-
return { type: "go", command: ["go", "build", "./..."], description: "Go (go build)" };
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Check for Python (pyproject.toml or pyrightconfig.json)
|
|
489
|
-
if (fs.existsSync(path.join(cwd, "pyproject.toml")) || fs.existsSync(path.join(cwd, "pyrightconfig.json"))) {
|
|
490
|
-
return { type: "python", command: ["pyright"], description: "Python (pyright)" };
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return { type: "unknown", description: "Unknown project type" };
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/** Run workspace diagnostics command and parse output */
|
|
497
|
-
async function runWorkspaceDiagnostics(
|
|
498
|
-
cwd: string,
|
|
499
|
-
signal?: AbortSignal,
|
|
500
|
-
): Promise<{ output: string; projectType: ProjectType }> {
|
|
501
|
-
throwIfAborted(signal);
|
|
502
|
-
const projectType = detectProjectType(cwd);
|
|
503
|
-
if (!projectType.command) {
|
|
504
|
-
return {
|
|
505
|
-
output: `Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.mod), Python (pyproject.toml)`,
|
|
506
|
-
projectType,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
const proc = Bun.spawn(projectType.command, {
|
|
510
|
-
cwd,
|
|
511
|
-
stdout: "pipe",
|
|
512
|
-
stderr: "pipe",
|
|
513
|
-
windowsHide: true,
|
|
514
|
-
});
|
|
515
|
-
const abortHandler = () => {
|
|
516
|
-
proc.kill();
|
|
517
|
-
};
|
|
518
|
-
if (signal) {
|
|
519
|
-
signal.addEventListener("abort", abortHandler, { once: true });
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
try {
|
|
523
|
-
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
524
|
-
await proc.exited;
|
|
525
|
-
throwIfAborted(signal);
|
|
526
|
-
const combined = (stdout + stderr).trim();
|
|
527
|
-
if (!combined) {
|
|
528
|
-
return { output: "No issues found", projectType };
|
|
529
|
-
}
|
|
530
|
-
// Limit output length
|
|
531
|
-
const lines = combined.split("\n");
|
|
532
|
-
if (lines.length > 50) {
|
|
533
|
-
return { output: `${lines.slice(0, 50).join("\n")}\n... and ${lines.length - 50} more lines`, projectType };
|
|
534
|
-
}
|
|
535
|
-
return { output: combined, projectType };
|
|
536
|
-
} catch (e) {
|
|
537
|
-
if (signal?.aborted) {
|
|
538
|
-
throw new ToolAbortError();
|
|
539
|
-
}
|
|
540
|
-
return { output: `Failed to run ${projectType.command.join(" ")}: ${e}`, projectType };
|
|
541
|
-
} finally {
|
|
542
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
464
|
/** Result from getDiagnosticsForFile */
|
|
547
465
|
export interface FileDiagnosticsResult {
|
|
548
466
|
/** Name of the LSP server used (if available) */
|
|
@@ -1235,16 +1153,14 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1235
1153
|
// Diagnostics can be batch or single-file - queries all applicable servers
|
|
1236
1154
|
if (action === "diagnostics") {
|
|
1237
1155
|
if (file === "*") {
|
|
1238
|
-
// `*` => run workspace diagnostics across all configured servers
|
|
1239
|
-
const result = await runWorkspaceDiagnostics(this.session.cwd, signal);
|
|
1240
1156
|
return {
|
|
1241
1157
|
content: [
|
|
1242
1158
|
{
|
|
1243
1159
|
type: "text",
|
|
1244
|
-
text:
|
|
1160
|
+
text: "Workspace build diagnostics are unavailable via lsp. Use a concrete file path or glob for language-server diagnostics, and use an execution-authorized tool to run build or typecheck commands.",
|
|
1245
1161
|
},
|
|
1246
1162
|
],
|
|
1247
|
-
details: { action, success:
|
|
1163
|
+
details: { action, success: false, request: params },
|
|
1248
1164
|
};
|
|
1249
1165
|
}
|
|
1250
1166
|
|
|
@@ -1253,7 +1169,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1253
1169
|
content: [
|
|
1254
1170
|
{
|
|
1255
1171
|
type: "text",
|
|
1256
|
-
text: "Error: file parameter required. Use
|
|
1172
|
+
text: "Error: file parameter required. Use a concrete file path or glob for diagnostics.",
|
|
1257
1173
|
},
|
|
1258
1174
|
],
|
|
1259
1175
|
details: { action, success: false, request: params },
|
|
@@ -1281,7 +1197,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1281
1197
|
const allServerNames = new Set<string>();
|
|
1282
1198
|
if (truncatedGlobTargets) {
|
|
1283
1199
|
results.push(
|
|
1284
|
-
`${theme.status.warning} Pattern matched more than ${MAX_GLOB_DIAGNOSTIC_TARGETS} files; showing first ${MAX_GLOB_DIAGNOSTIC_TARGETS}. Narrow the glob
|
|
1200
|
+
`${theme.status.warning} Pattern matched more than ${MAX_GLOB_DIAGNOSTIC_TARGETS} files; showing first ${MAX_GLOB_DIAGNOSTIC_TARGETS}. Narrow the glob for broader coverage.`,
|
|
1285
1201
|
);
|
|
1286
1202
|
}
|
|
1287
1203
|
|