@ian-pascoe/pi-dap 0.1.0 → 0.3.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/package.json +10 -9
- package/skills/pi-dap/SKILL.md +16 -0
- package/src/dap-protocol-client.ts +5 -13
- package/src/dap-session-files.ts +16 -26
- package/src/dap-session.ts +49 -145
- package/src/dap-tool.ts +27 -27
- package/src/pi-dap-extension.ts +7 -17
- package/src/pi-dap-settings.ts +14 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ian-pascoe/pi-dap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Configured Debug Adapter Protocol sessions for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"src",
|
|
26
|
+
"skills",
|
|
26
27
|
"README.md",
|
|
27
28
|
"LICENSE"
|
|
28
29
|
],
|
|
@@ -31,16 +32,9 @@
|
|
|
31
32
|
"access": "public",
|
|
32
33
|
"provenance": true
|
|
33
34
|
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
36
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
37
|
-
},
|
|
38
35
|
"dependencies": {
|
|
39
36
|
"@vscode/debugprotocol": "^1.68.0"
|
|
40
37
|
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"vscode-js-debug": "https://github.com/microsoft/vscode-js-debug/releases/download/v1.117.0/js-debug-dap-v1.117.0.tar.gz"
|
|
43
|
-
},
|
|
44
38
|
"peerDependencies": {
|
|
45
39
|
"@earendil-works/pi-coding-agent": "*",
|
|
46
40
|
"@earendil-works/pi-tui": "*",
|
|
@@ -52,6 +46,13 @@
|
|
|
52
46
|
"pi": {
|
|
53
47
|
"extensions": [
|
|
54
48
|
"./src/index.ts"
|
|
49
|
+
],
|
|
50
|
+
"skills": [
|
|
51
|
+
"./skills"
|
|
55
52
|
]
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
56
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
56
57
|
}
|
|
57
|
-
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-dap
|
|
3
|
+
description: Configure or diagnose Pi DAP when an Adapter Definition or Launch Profile fails, a Debug Session is stuck, or breakpoints do not bind.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pi DAP
|
|
8
|
+
|
|
9
|
+
1. Read [`../../README.md`](../../README.md)'s Settings section and identify the effective Adapter Definition and Launch Profile.
|
|
10
|
+
2. Call `dap` with `{"operation":"status"}`, then inspect settings warnings and retained adapter stderr.
|
|
11
|
+
3. Verify the configured command and referenced files without launching.
|
|
12
|
+
4. Classify the failure as settings, adapter startup, protocol, state, timeout, or source mapping.
|
|
13
|
+
5. For a requested change, edit one settings layer, validate JSON, and reload Pi.
|
|
14
|
+
6. With approval, run one representative launch. Finish when it reaches the expected Debug Session state or one exact adapter or protocol failure remains.
|
|
15
|
+
|
|
16
|
+
Ask before starting, pausing, stopping, or otherwise changing a Debuggee. An execution timeout may leave it running, so inspect `status` first.
|
|
@@ -126,8 +126,6 @@ export interface DapProtocolEventWaitOptions {
|
|
|
126
126
|
readonly signal?: AbortSignal | undefined;
|
|
127
127
|
/** Override the configured ordinary request budget. */
|
|
128
128
|
readonly timeoutMs?: number | undefined;
|
|
129
|
-
/** Accept only matching event payloads after matching the event name. */
|
|
130
|
-
readonly predicate?: (event: DebugProtocol.Event) => boolean;
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
/** Overrides used when a TCP Debug Adapter asks for its primary target channel. */
|
|
@@ -139,8 +137,6 @@ export interface DapProtocolTargetChannelOptions {
|
|
|
139
137
|
|
|
140
138
|
/** Classified Debug Adapter process, transport, protocol, timeout, and cancellation failure. */
|
|
141
139
|
export class DapProtocolClientError extends Error {
|
|
142
|
-
readonly _tag = "DapProtocolClientError" as const;
|
|
143
|
-
|
|
144
140
|
/** Construct a searchable DAP client failure that always names its stderr capture. */
|
|
145
141
|
constructor(
|
|
146
142
|
readonly kind:
|
|
@@ -252,7 +248,8 @@ function isDapStartupCancelled(options: DapProtocolClientOptions): boolean {
|
|
|
252
248
|
return options.startupSignal?.aborted ?? false;
|
|
253
249
|
}
|
|
254
250
|
|
|
255
|
-
|
|
251
|
+
/** Merge configured environment overrides over the parent environment; null removes an inherited key. */
|
|
252
|
+
export function resolvedAdapterEnvironment(
|
|
256
253
|
configured: Readonly<Record<string, string | null>>,
|
|
257
254
|
): NodeJS.ProcessEnv {
|
|
258
255
|
const environment: NodeJS.ProcessEnv = { ...process.env };
|
|
@@ -468,7 +465,8 @@ function processExitPromise(child: ChildProcessWithoutNullStreams): Promise<void
|
|
|
468
465
|
return new Promise((resolve) => child.once("exit", () => resolve()));
|
|
469
466
|
}
|
|
470
467
|
|
|
471
|
-
|
|
468
|
+
/** Signal one owned child's Linux process group (or the process itself elsewhere). */
|
|
469
|
+
export function signalOwnedProcessGroup(
|
|
472
470
|
child: ChildProcessWithoutNullStreams,
|
|
473
471
|
signal: NodeJS.Signals,
|
|
474
472
|
): void {
|
|
@@ -724,11 +722,6 @@ export class DapProtocolClient {
|
|
|
724
722
|
return this.options.stderrPath;
|
|
725
723
|
}
|
|
726
724
|
|
|
727
|
-
/** Whether the transport has failed or shutdown has begun. */
|
|
728
|
-
get isClosed(): boolean {
|
|
729
|
-
return this.failure !== undefined || this.shuttingDown;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
725
|
/** Connect the primary adapter-owned target channel without spawning another process. */
|
|
733
726
|
async connectTargetChannel(
|
|
734
727
|
overrides: DapProtocolTargetChannelOptions = {},
|
|
@@ -787,7 +780,7 @@ export class DapProtocolClient {
|
|
|
787
780
|
this.pendingEventWaitRejectors.delete(rejectWait);
|
|
788
781
|
};
|
|
789
782
|
const onEvent = (event: DebugProtocol.Event) => {
|
|
790
|
-
if (event.event !== eventName
|
|
783
|
+
if (event.event !== eventName) return;
|
|
791
784
|
cleanup();
|
|
792
785
|
// SAFETY: The caller chooses TEvent for the named DAP event; every envelope was parsed before this protocol boundary.
|
|
793
786
|
resolve(event as TEvent);
|
|
@@ -918,7 +911,6 @@ export class DapProtocolClient {
|
|
|
918
911
|
if (this.socket === undefined) this.child?.stdin.end();
|
|
919
912
|
|
|
920
913
|
if (this.child === undefined) {
|
|
921
|
-
this.shuttingDown = true;
|
|
922
914
|
this.reader.removeAllListeners();
|
|
923
915
|
this.writer.destroy();
|
|
924
916
|
return;
|
package/src/dap-session-files.ts
CHANGED
|
@@ -11,29 +11,11 @@ export interface DapOutputDrain {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/** Retains only bounded unread Debuggee output until a tool operation drains it. */
|
|
14
|
-
export
|
|
15
|
-
/** Append Debuggee output in protocol event order. */
|
|
16
|
-
append(output: string): void;
|
|
17
|
-
/** Return and clear all currently unread Debuggee output. */
|
|
18
|
-
drain(): DapOutputDrain;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Owns private Result Spill and adapter stderr paths for one Pi session. */
|
|
22
|
-
export interface DapSessionFiles {
|
|
23
|
-
/** Private directory removed when the Pi session shuts down. */
|
|
24
|
-
readonly directoryPath: string;
|
|
25
|
-
/** Write complete truncated tool output to a Result Spill file. */
|
|
26
|
-
writeResultSpill(output: string): Promise<string>;
|
|
27
|
-
/** Create a private stderr file path for one Debug Adapter launch. */
|
|
28
|
-
getAdapterStderrPath(adapterId: string): Promise<string>;
|
|
29
|
-
/** Remove all session files after queued writes finish. */
|
|
30
|
-
close(): Promise<void>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class RetainedDapOutput implements DapOutputBuffer {
|
|
14
|
+
export class RetainedDapOutput {
|
|
34
15
|
private content = Buffer.alloc(0);
|
|
35
16
|
private discardedBytes = 0;
|
|
36
17
|
|
|
18
|
+
/** Append Debuggee output in protocol event order. */
|
|
37
19
|
append(output: string): void {
|
|
38
20
|
const combined = Buffer.concat([this.content, Buffer.from(output)]);
|
|
39
21
|
const overflow = Math.max(0, combined.length - MAX_DAP_RETAINED_BYTES);
|
|
@@ -41,6 +23,7 @@ class RetainedDapOutput implements DapOutputBuffer {
|
|
|
41
23
|
this.content = overflow === 0 ? combined : Buffer.from(combined.subarray(overflow));
|
|
42
24
|
}
|
|
43
25
|
|
|
26
|
+
/** Return and clear all currently unread Debuggee output. */
|
|
44
27
|
drain(): DapOutputDrain {
|
|
45
28
|
const drained = { discardedBytes: this.discardedBytes, text: this.content.toString("utf8") };
|
|
46
29
|
this.content = Buffer.alloc(0);
|
|
@@ -49,6 +32,18 @@ class RetainedDapOutput implements DapOutputBuffer {
|
|
|
49
32
|
}
|
|
50
33
|
}
|
|
51
34
|
|
|
35
|
+
/** Owns private Result Spill and adapter stderr paths for one Pi session. */
|
|
36
|
+
export interface DapSessionFiles {
|
|
37
|
+
/** Private directory removed when the Pi session shuts down. */
|
|
38
|
+
readonly directoryPath: string;
|
|
39
|
+
/** Write complete truncated tool output to a Result Spill file. */
|
|
40
|
+
writeResultSpill(output: string): Promise<string>;
|
|
41
|
+
/** Create a private stderr file path for one Debug Adapter launch. */
|
|
42
|
+
getAdapterStderrPath(): Promise<string>;
|
|
43
|
+
/** Remove all session files after queued writes finish. */
|
|
44
|
+
close(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
52
47
|
class DapSessionFileStore implements DapSessionFiles {
|
|
53
48
|
private closed = false;
|
|
54
49
|
private closePromise: Promise<void> | undefined;
|
|
@@ -66,7 +61,7 @@ class DapSessionFileStore implements DapSessionFiles {
|
|
|
66
61
|
});
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
getAdapterStderrPath(
|
|
64
|
+
getAdapterStderrPath(): Promise<string> {
|
|
70
65
|
const path = join(this.directoryPath, `adapter-stderr-${this.nextFileIndex++}.log`);
|
|
71
66
|
return this.enqueueWrite(async () => {
|
|
72
67
|
await writeFile(path, "", { mode: 0o600 });
|
|
@@ -96,11 +91,6 @@ class DapSessionFileStore implements DapSessionFiles {
|
|
|
96
91
|
}
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
/** Create a bounded unread Debuggee output buffer. */
|
|
100
|
-
export function createDapOutputBuffer(): DapOutputBuffer {
|
|
101
|
-
return new RetainedDapOutput();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
94
|
/** Create a private directory for Result Spills and Debug Adapter stderr. */
|
|
105
95
|
export async function createDapSessionFiles(sessionDirectory: string): Promise<DapSessionFiles> {
|
|
106
96
|
await mkdir(sessionDirectory, { mode: 0o700, recursive: true });
|
package/src/dap-session.ts
CHANGED
|
@@ -7,15 +7,13 @@ import { Value } from "typebox/value";
|
|
|
7
7
|
import {
|
|
8
8
|
DapProtocolClient,
|
|
9
9
|
DapProtocolClientError,
|
|
10
|
+
resolvedAdapterEnvironment,
|
|
11
|
+
signalOwnedProcessGroup,
|
|
10
12
|
type DapProtocolRequestOptions,
|
|
11
13
|
type DapProtocolTransport,
|
|
12
14
|
type DapReverseRequestResult,
|
|
13
15
|
} from "./dap-protocol-client.js";
|
|
14
|
-
import {
|
|
15
|
-
createDapOutputBuffer,
|
|
16
|
-
type DapOutputBuffer,
|
|
17
|
-
type DapSessionFiles,
|
|
18
|
-
} from "./dap-session-files.js";
|
|
16
|
+
import { RetainedDapOutput, type DapSessionFiles } from "./dap-session-files.js";
|
|
19
17
|
import type {
|
|
20
18
|
DapAdapterDefinition,
|
|
21
19
|
DapLaunchProfile,
|
|
@@ -162,8 +160,6 @@ const JsDebugPrimaryTargetArgumentsSchema = Type.Object(
|
|
|
162
160
|
|
|
163
161
|
/** Classified configuration, state, protocol, or Debug Adapter failure. */
|
|
164
162
|
export class DapSessionError extends Error {
|
|
165
|
-
readonly _tag = "DapSessionError" as const;
|
|
166
|
-
|
|
167
163
|
/** Construct a stable Debug Session failure for the Pi tool boundary. */
|
|
168
164
|
constructor(
|
|
169
165
|
readonly kind: "adapter" | "configuration" | "protocol" | "state",
|
|
@@ -282,14 +278,6 @@ export interface DapSessionOptions {
|
|
|
282
278
|
readonly onUnexpectedFailure?: (error: Error) => void;
|
|
283
279
|
}
|
|
284
280
|
|
|
285
|
-
type DapLaunchArgumentValue =
|
|
286
|
-
| null
|
|
287
|
-
| boolean
|
|
288
|
-
| number
|
|
289
|
-
| string
|
|
290
|
-
| readonly DapLaunchArgumentValue[]
|
|
291
|
-
| { readonly [key: string]: DapLaunchArgumentValue };
|
|
292
|
-
|
|
293
281
|
interface ActiveDapSession {
|
|
294
282
|
readonly adapter: DapAdapterDefinition;
|
|
295
283
|
readonly profile: DapLaunchProfile;
|
|
@@ -308,19 +296,17 @@ interface ActiveDapSession {
|
|
|
308
296
|
stopping: boolean;
|
|
309
297
|
}
|
|
310
298
|
|
|
311
|
-
interface TerminatedDapSessionState {
|
|
312
|
-
readonly kind: "terminated";
|
|
313
|
-
readonly adapterId: string;
|
|
314
|
-
readonly profileId: string;
|
|
315
|
-
readonly exitCode?: number;
|
|
316
|
-
readonly terminationReason?: string;
|
|
317
|
-
readonly cleanupPromise: Promise<void>;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
299
|
type InternalDapSessionState =
|
|
321
300
|
| { readonly kind: "idle" }
|
|
322
301
|
| { readonly kind: "active"; readonly active: ActiveDapSession }
|
|
323
|
-
|
|
|
302
|
+
| {
|
|
303
|
+
readonly kind: "terminated";
|
|
304
|
+
readonly adapterId: string;
|
|
305
|
+
readonly profileId: string;
|
|
306
|
+
readonly exitCode?: number;
|
|
307
|
+
readonly terminationReason?: string;
|
|
308
|
+
readonly cleanupPromise: Promise<void>;
|
|
309
|
+
};
|
|
324
310
|
|
|
325
311
|
interface ExecutionWait {
|
|
326
312
|
readonly promise: Promise<"cancelled" | "timeout" | "transition">;
|
|
@@ -352,7 +338,8 @@ function supportsJsDebugPrimaryTarget(
|
|
|
352
338
|
return adapter.transport.type === "tcp" && profile.arguments.type === "pwa-node";
|
|
353
339
|
}
|
|
354
340
|
|
|
355
|
-
|
|
341
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- The only check is an instanceof narrow against the classified protocol error.
|
|
342
|
+
function isProtocolCancellation(error: unknown): boolean {
|
|
356
343
|
return error instanceof DapProtocolClientError && error.kind === "cancelled";
|
|
357
344
|
}
|
|
358
345
|
|
|
@@ -366,47 +353,6 @@ function dapRequestOptions(
|
|
|
366
353
|
return { signal, timeoutMs };
|
|
367
354
|
}
|
|
368
355
|
|
|
369
|
-
function terminatedDapSessionState(
|
|
370
|
-
active: ActiveDapSession,
|
|
371
|
-
cleanupPromise: Promise<void>,
|
|
372
|
-
terminationReason: string,
|
|
373
|
-
): TerminatedDapSessionState {
|
|
374
|
-
if (active.exitCode !== undefined && terminationReason.length > 0) {
|
|
375
|
-
return {
|
|
376
|
-
kind: "terminated",
|
|
377
|
-
adapterId: active.adapter.id,
|
|
378
|
-
profileId: active.profile.id,
|
|
379
|
-
cleanupPromise,
|
|
380
|
-
exitCode: active.exitCode,
|
|
381
|
-
terminationReason,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
if (active.exitCode !== undefined) {
|
|
385
|
-
return {
|
|
386
|
-
kind: "terminated",
|
|
387
|
-
adapterId: active.adapter.id,
|
|
388
|
-
profileId: active.profile.id,
|
|
389
|
-
cleanupPromise,
|
|
390
|
-
exitCode: active.exitCode,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
if (terminationReason.length > 0) {
|
|
394
|
-
return {
|
|
395
|
-
kind: "terminated",
|
|
396
|
-
adapterId: active.adapter.id,
|
|
397
|
-
profileId: active.profile.id,
|
|
398
|
-
cleanupPromise,
|
|
399
|
-
terminationReason,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
return {
|
|
403
|
-
kind: "terminated",
|
|
404
|
-
adapterId: active.adapter.id,
|
|
405
|
-
profileId: active.profile.id,
|
|
406
|
-
cleanupPromise,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
356
|
function isProcessAlive(pid: number): boolean {
|
|
411
357
|
try {
|
|
412
358
|
process.kill(pid, 0);
|
|
@@ -422,9 +368,8 @@ async function stopOwnedDebuggeeProcess(
|
|
|
422
368
|
): Promise<void> {
|
|
423
369
|
const pid = child.pid;
|
|
424
370
|
if (pid === undefined || !isProcessAlive(pid)) return;
|
|
425
|
-
const processId = process.platform === "linux" ? -pid : pid;
|
|
426
371
|
try {
|
|
427
|
-
|
|
372
|
+
signalOwnedProcessGroup(child, "SIGTERM");
|
|
428
373
|
} catch {
|
|
429
374
|
return;
|
|
430
375
|
}
|
|
@@ -432,7 +377,7 @@ async function stopOwnedDebuggeeProcess(
|
|
|
432
377
|
while (Date.now() < deadline && isProcessAlive(pid)) await delay(10);
|
|
433
378
|
if (!isProcessAlive(pid)) return;
|
|
434
379
|
try {
|
|
435
|
-
|
|
380
|
+
signalOwnedProcessGroup(child, "SIGKILL");
|
|
436
381
|
} catch {
|
|
437
382
|
// The Debuggee exited between the liveness check and the signal.
|
|
438
383
|
}
|
|
@@ -440,7 +385,7 @@ async function stopOwnedDebuggeeProcess(
|
|
|
440
385
|
|
|
441
386
|
/** Own one configured Debug Session at a time and retain Desired Breakpoints across launches. */
|
|
442
387
|
export class DapSession {
|
|
443
|
-
private readonly output
|
|
388
|
+
private readonly output = new RetainedDapOutput();
|
|
444
389
|
private readonly desiredBreakpoints = new Map<string, readonly DapDesiredBreakpoint[]>();
|
|
445
390
|
private readonly executionWaiters = new Set<() => void>();
|
|
446
391
|
private state: InternalDapSessionState = { kind: "idle" };
|
|
@@ -465,9 +410,8 @@ export class DapSession {
|
|
|
465
410
|
`Launch Profile ${profile.id} references unavailable Adapter Definition ${profile.adapterId}`,
|
|
466
411
|
);
|
|
467
412
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
);
|
|
413
|
+
// ponytail: shallow copy suffices — only top-level launch keys are replaced below.
|
|
414
|
+
const launchArguments = { ...profile.arguments };
|
|
471
415
|
const adapterProtocolId = Value.Check(DapAdapterProtocolIdSchema, profile.arguments.type)
|
|
472
416
|
? profile.arguments.type
|
|
473
417
|
: adapter.id;
|
|
@@ -477,7 +421,7 @@ export class DapSession {
|
|
|
477
421
|
if (input.cwd !== undefined) launchArguments.cwd = resolve(this.options.cwd, input.cwd);
|
|
478
422
|
|
|
479
423
|
const debuggeeProcesses = new Set<ChildProcessWithoutNullStreams>();
|
|
480
|
-
const stderrPath = await this.options.sessionFiles.getAdapterStderrPath(
|
|
424
|
+
const stderrPath = await this.options.sessionFiles.getAdapterStderrPath();
|
|
481
425
|
let active: ActiveDapSession | undefined;
|
|
482
426
|
try {
|
|
483
427
|
const client = await DapProtocolClient.start({
|
|
@@ -581,7 +525,6 @@ export class DapSession {
|
|
|
581
525
|
return this.result();
|
|
582
526
|
} catch (cause) {
|
|
583
527
|
if (active !== undefined) {
|
|
584
|
-
active.stopping = true;
|
|
585
528
|
await this.finishActiveSession(active, "launch failed");
|
|
586
529
|
} else {
|
|
587
530
|
await Promise.all(
|
|
@@ -592,7 +535,7 @@ export class DapSession {
|
|
|
592
535
|
this.state = { kind: "idle" };
|
|
593
536
|
this.publishSnapshot();
|
|
594
537
|
}
|
|
595
|
-
if (
|
|
538
|
+
if (isProtocolCancellation(cause)) {
|
|
596
539
|
throw new DapSessionError("adapter", "launch was cancelled and cleaned up", { cause });
|
|
597
540
|
}
|
|
598
541
|
if (cause instanceof DapSessionError) throw cause;
|
|
@@ -623,39 +566,27 @@ export class DapSession {
|
|
|
623
566
|
|
|
624
567
|
/** Continue a stopped Debuggee and wait for its next stop or termination. */
|
|
625
568
|
continue(signal?: AbortSignal): Promise<DapSessionResult> {
|
|
626
|
-
return this.
|
|
569
|
+
return this.executeLifecycleRequest("continue", signal);
|
|
627
570
|
}
|
|
628
571
|
|
|
629
572
|
/** Step over in a stopped Debuggee and wait for its next stop or termination. */
|
|
630
573
|
next(signal?: AbortSignal): Promise<DapSessionResult> {
|
|
631
|
-
return this.
|
|
574
|
+
return this.executeLifecycleRequest("next", signal);
|
|
632
575
|
}
|
|
633
576
|
|
|
634
577
|
/** Step into in a stopped Debuggee and wait for its next stop or termination. */
|
|
635
578
|
stepIn(signal?: AbortSignal): Promise<DapSessionResult> {
|
|
636
|
-
return this.
|
|
579
|
+
return this.executeLifecycleRequest("stepIn", signal);
|
|
637
580
|
}
|
|
638
581
|
|
|
639
582
|
/** Step out in a stopped Debuggee and wait for its next stop or termination. */
|
|
640
583
|
stepOut(signal?: AbortSignal): Promise<DapSessionResult> {
|
|
641
|
-
return this.
|
|
584
|
+
return this.executeLifecycleRequest("stepOut", signal);
|
|
642
585
|
}
|
|
643
586
|
|
|
644
587
|
/** Pause a running Debuggee and wait for its stopped event. */
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const threadId = await this.resolveThreadId(active, signal);
|
|
648
|
-
const wait = this.waitForExecutionTransition(signal);
|
|
649
|
-
try {
|
|
650
|
-
await active.client.request("pause", { threadId }, dapRequestOptions(signal));
|
|
651
|
-
} catch (cause) {
|
|
652
|
-
wait.cancel();
|
|
653
|
-
if (cause instanceof Error && isProtocolCancellation(cause)) return this.result();
|
|
654
|
-
throw cause;
|
|
655
|
-
}
|
|
656
|
-
await wait.promise;
|
|
657
|
-
await active.cleanupPromise;
|
|
658
|
-
return this.result();
|
|
588
|
+
pause(signal?: AbortSignal): Promise<DapSessionResult> {
|
|
589
|
+
return this.executeLifecycleRequest("pause", signal);
|
|
659
590
|
}
|
|
660
591
|
|
|
661
592
|
/** Retrieve a page of Stack Frames from the stopped thread. */
|
|
@@ -745,13 +676,12 @@ export class DapSession {
|
|
|
745
676
|
}
|
|
746
677
|
|
|
747
678
|
/** Idempotently stop the active Debug Session and preserve Desired Breakpoints. */
|
|
748
|
-
async stop(
|
|
679
|
+
async stop(): Promise<DapSessionResult> {
|
|
749
680
|
const active = this.currentActive();
|
|
750
681
|
if (active === undefined) {
|
|
751
682
|
if (this.state.kind === "terminated") await this.state.cleanupPromise;
|
|
752
683
|
return this.result();
|
|
753
684
|
}
|
|
754
|
-
active.stopping = true;
|
|
755
685
|
await this.finishActiveSession(active, "stopped by request");
|
|
756
686
|
return this.result();
|
|
757
687
|
}
|
|
@@ -762,7 +692,6 @@ export class DapSession {
|
|
|
762
692
|
this.shutdownPromise = (async () => {
|
|
763
693
|
const active = this.currentActive();
|
|
764
694
|
if (active !== undefined) {
|
|
765
|
-
active.stopping = true;
|
|
766
695
|
await this.finishActiveSession(active, "Pi session shutdown");
|
|
767
696
|
} else if (this.state.kind === "terminated") {
|
|
768
697
|
await this.state.cleanupPromise;
|
|
@@ -824,19 +753,19 @@ export class DapSession {
|
|
|
824
753
|
return response;
|
|
825
754
|
}
|
|
826
755
|
|
|
827
|
-
private async
|
|
828
|
-
command: "continue" | "next" | "stepIn" | "stepOut",
|
|
756
|
+
private async executeLifecycleRequest(
|
|
757
|
+
command: "continue" | "next" | "stepIn" | "stepOut" | "pause",
|
|
829
758
|
signal: AbortSignal | undefined,
|
|
830
759
|
): Promise<DapSessionResult> {
|
|
831
|
-
const active = this.requireActivePhase("stopped", command);
|
|
760
|
+
const active = this.requireActivePhase(command === "pause" ? "running" : "stopped", command);
|
|
832
761
|
const threadId = await this.resolveThreadId(active, signal);
|
|
833
|
-
this.transitionActiveToRunning(active);
|
|
762
|
+
if (command !== "pause") this.transitionActiveToRunning(active);
|
|
834
763
|
const wait = this.waitForExecutionTransition(signal);
|
|
835
764
|
try {
|
|
836
765
|
await active.client.request(command, { threadId }, dapRequestOptions(signal));
|
|
837
766
|
} catch (cause) {
|
|
838
767
|
wait.cancel();
|
|
839
|
-
if (
|
|
768
|
+
if (isProtocolCancellation(cause)) return this.result();
|
|
840
769
|
throw cause;
|
|
841
770
|
}
|
|
842
771
|
await wait.promise;
|
|
@@ -1029,11 +958,7 @@ export class DapSession {
|
|
|
1029
958
|
argumentsValue: Static<typeof RunInTerminalArgumentsSchema>,
|
|
1030
959
|
debuggeeProcesses: Set<ChildProcessWithoutNullStreams>,
|
|
1031
960
|
): Promise<DapReverseRequestResult> {
|
|
1032
|
-
const environment
|
|
1033
|
-
for (const [name, value] of Object.entries(argumentsValue.env ?? {})) {
|
|
1034
|
-
if (value === null) delete environment[name];
|
|
1035
|
-
else environment[name] = value;
|
|
1036
|
-
}
|
|
961
|
+
const environment = resolvedAdapterEnvironment(argumentsValue.env ?? {});
|
|
1037
962
|
const interpretedByShell = argumentsValue.argsCanBeInterpretedByShell === true;
|
|
1038
963
|
const command = interpretedByShell ? argumentsValue.args.join(" ") : argumentsValue.args[0];
|
|
1039
964
|
if (command === undefined)
|
|
@@ -1121,7 +1046,14 @@ export class DapSession {
|
|
|
1121
1046
|
})();
|
|
1122
1047
|
active.cleanupPromise = cleanup;
|
|
1123
1048
|
if (this.isCurrentActive(active)) {
|
|
1124
|
-
this.state =
|
|
1049
|
+
this.state = {
|
|
1050
|
+
kind: "terminated",
|
|
1051
|
+
adapterId: active.adapter.id,
|
|
1052
|
+
profileId: active.profile.id,
|
|
1053
|
+
cleanupPromise: cleanup,
|
|
1054
|
+
...(active.exitCode !== undefined && { exitCode: active.exitCode }),
|
|
1055
|
+
...(terminationReason.length > 0 && { terminationReason }),
|
|
1056
|
+
};
|
|
1125
1057
|
this.publishSnapshot();
|
|
1126
1058
|
}
|
|
1127
1059
|
this.settleExecutionWaiters();
|
|
@@ -1155,53 +1087,25 @@ export class DapSession {
|
|
|
1155
1087
|
private snapshot(): DapSessionSnapshot {
|
|
1156
1088
|
if (this.state.kind === "idle") return { state: "idle" };
|
|
1157
1089
|
if (this.state.kind === "terminated") {
|
|
1158
|
-
|
|
1159
|
-
return {
|
|
1160
|
-
state: "terminated",
|
|
1161
|
-
adapterId: this.state.adapterId,
|
|
1162
|
-
profileId: this.state.profileId,
|
|
1163
|
-
exitCode: this.state.exitCode,
|
|
1164
|
-
terminationReason: this.state.terminationReason,
|
|
1165
|
-
};
|
|
1166
|
-
}
|
|
1167
|
-
if (this.state.exitCode !== undefined) {
|
|
1168
|
-
return {
|
|
1169
|
-
state: "terminated",
|
|
1170
|
-
adapterId: this.state.adapterId,
|
|
1171
|
-
profileId: this.state.profileId,
|
|
1172
|
-
exitCode: this.state.exitCode,
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
if (this.state.terminationReason !== undefined) {
|
|
1176
|
-
return {
|
|
1177
|
-
state: "terminated",
|
|
1178
|
-
adapterId: this.state.adapterId,
|
|
1179
|
-
profileId: this.state.profileId,
|
|
1180
|
-
terminationReason: this.state.terminationReason,
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1090
|
+
const terminated = this.state;
|
|
1183
1091
|
return {
|
|
1184
1092
|
state: "terminated",
|
|
1185
|
-
adapterId:
|
|
1186
|
-
profileId:
|
|
1093
|
+
adapterId: terminated.adapterId,
|
|
1094
|
+
profileId: terminated.profileId,
|
|
1095
|
+
...(terminated.exitCode !== undefined && { exitCode: terminated.exitCode }),
|
|
1096
|
+
...(terminated.terminationReason !== undefined && {
|
|
1097
|
+
terminationReason: terminated.terminationReason,
|
|
1098
|
+
}),
|
|
1187
1099
|
};
|
|
1188
1100
|
}
|
|
1189
1101
|
const active = this.state.active;
|
|
1190
1102
|
if (active.phase === "stopped") {
|
|
1191
|
-
if (active.threadId !== undefined) {
|
|
1192
|
-
return {
|
|
1193
|
-
state: "stopped",
|
|
1194
|
-
adapterId: active.adapter.id,
|
|
1195
|
-
profileId: active.profile.id,
|
|
1196
|
-
stopReason: active.stopReason ?? "unknown",
|
|
1197
|
-
threadId: active.threadId,
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
1103
|
return {
|
|
1201
1104
|
state: "stopped",
|
|
1202
1105
|
adapterId: active.adapter.id,
|
|
1203
1106
|
profileId: active.profile.id,
|
|
1204
1107
|
stopReason: active.stopReason ?? "unknown",
|
|
1108
|
+
...(active.threadId !== undefined && { threadId: active.threadId }),
|
|
1205
1109
|
};
|
|
1206
1110
|
}
|
|
1207
1111
|
return {
|
package/src/dap-tool.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
DEFAULT_MAX_LINES,
|
|
5
5
|
truncateHead,
|
|
6
6
|
type AgentToolResult,
|
|
7
|
-
type ExtensionAPI,
|
|
8
7
|
type ToolDefinition,
|
|
9
8
|
} from "@earendil-works/pi-coding-agent";
|
|
10
9
|
import { Value } from "typebox/value";
|
|
@@ -14,7 +13,6 @@ import type {
|
|
|
14
13
|
DapSession,
|
|
15
14
|
DapSessionResult,
|
|
16
15
|
DapStackInput,
|
|
17
|
-
DapVariablesInput,
|
|
18
16
|
} from "./dap-session.js";
|
|
19
17
|
import type { DapSessionFiles } from "./dap-session-files.js";
|
|
20
18
|
import {
|
|
@@ -27,6 +25,10 @@ import {
|
|
|
27
25
|
} from "./dap-tool-contract.js";
|
|
28
26
|
import { renderDapToolCall, renderDapToolResult } from "./dap-tool-rendering.js";
|
|
29
27
|
|
|
28
|
+
type DapToolDefinition = ToolDefinition<typeof DapToolParametersSchema, DapToolRenderDetails> & {
|
|
29
|
+
readonly outputSchema: typeof DapToolResultDetailsSchema;
|
|
30
|
+
};
|
|
31
|
+
|
|
30
32
|
type DapToolSession = Pick<
|
|
31
33
|
DapSession,
|
|
32
34
|
| "launch"
|
|
@@ -288,13 +290,14 @@ async function dispatchDapOperation(
|
|
|
288
290
|
): Promise<DapSessionResult> {
|
|
289
291
|
switch (parameters.operation) {
|
|
290
292
|
case "launch": {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
const input: DapLaunchInput = {
|
|
294
|
+
...(parameters.profile !== undefined && { profile: parameters.profile }),
|
|
295
|
+
...(parameters.program !== undefined && {
|
|
296
|
+
program: resolve(cwd, parameters.program),
|
|
297
|
+
}),
|
|
298
|
+
...(parameters.args !== undefined && { args: parameters.args }),
|
|
299
|
+
...(parameters.cwd !== undefined && { cwd: resolve(cwd, parameters.cwd) }),
|
|
300
|
+
};
|
|
298
301
|
return session.launch(input, signal);
|
|
299
302
|
}
|
|
300
303
|
case "set_breakpoints":
|
|
@@ -313,16 +316,18 @@ async function dispatchDapOperation(
|
|
|
313
316
|
case "pause":
|
|
314
317
|
return session.pause(signal);
|
|
315
318
|
case "stack": {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
319
|
+
const input: DapStackInput = {
|
|
320
|
+
...(parameters.thread_id !== undefined && { threadId: parameters.thread_id }),
|
|
321
|
+
...(parameters.start !== undefined && { start: parameters.start }),
|
|
322
|
+
...(parameters.count !== undefined && { count: parameters.count }),
|
|
323
|
+
};
|
|
320
324
|
return session.stack(input, signal);
|
|
321
325
|
}
|
|
322
326
|
case "variables": {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
327
|
+
const page = {
|
|
328
|
+
...(parameters.start !== undefined && { start: parameters.start }),
|
|
329
|
+
...(parameters.count !== undefined && { count: parameters.count }),
|
|
330
|
+
};
|
|
326
331
|
return "frame_id" in parameters
|
|
327
332
|
? session.variables({ ...page, frameId: parameters.frame_id }, signal)
|
|
328
333
|
: session.variables(
|
|
@@ -331,8 +336,10 @@ async function dispatchDapOperation(
|
|
|
331
336
|
);
|
|
332
337
|
}
|
|
333
338
|
case "evaluate": {
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
const input: DapEvaluateInput = {
|
|
340
|
+
expression: parameters.expression,
|
|
341
|
+
...(parameters.frame_id !== undefined && { frameId: parameters.frame_id }),
|
|
342
|
+
};
|
|
336
343
|
return session.evaluate(input, signal);
|
|
337
344
|
}
|
|
338
345
|
case "status":
|
|
@@ -353,7 +360,7 @@ function notifyDapToolObserver(operation: () => void): void {
|
|
|
353
360
|
/** Create the single strict Pi DAP ToolDefinition bound to current session resources. */
|
|
354
361
|
export function createDapToolDefinition(
|
|
355
362
|
getRuntime: () => DapToolRuntime | undefined,
|
|
356
|
-
):
|
|
363
|
+
): DapToolDefinition {
|
|
357
364
|
return {
|
|
358
365
|
name: "dap",
|
|
359
366
|
label: "DAP",
|
|
@@ -364,6 +371,7 @@ export function createDapToolDefinition(
|
|
|
364
371
|
"Use dap to set source breakpoints, launch a configured Debug Session, control the Debuggee, and inspect stopped Stack Frames and variables.",
|
|
365
372
|
],
|
|
366
373
|
parameters: DapToolParametersSchema,
|
|
374
|
+
outputSchema: DapToolResultDetailsSchema,
|
|
367
375
|
renderCall: (argumentsValue, theme, context) =>
|
|
368
376
|
renderDapToolCall(argumentsValue, theme, context.expanded, context.cwd),
|
|
369
377
|
renderResult: (result, options, theme, context) =>
|
|
@@ -410,11 +418,3 @@ export function createDapToolDefinition(
|
|
|
410
418
|
},
|
|
411
419
|
};
|
|
412
420
|
}
|
|
413
|
-
|
|
414
|
-
/** Register exactly one strict `dap` tool whose runtime follows Pi session reloads. */
|
|
415
|
-
export function registerDapTool(
|
|
416
|
-
pi: Pick<ExtensionAPI, "registerTool">,
|
|
417
|
-
getRuntime: () => DapToolRuntime | undefined,
|
|
418
|
-
): void {
|
|
419
|
-
pi.registerTool(createDapToolDefinition(getRuntime));
|
|
420
|
-
}
|
package/src/pi-dap-extension.ts
CHANGED
|
@@ -8,35 +8,25 @@ import {
|
|
|
8
8
|
import { DapSession } from "./dap-session.js";
|
|
9
9
|
import { createDapSessionFiles, type DapSessionFiles } from "./dap-session-files.js";
|
|
10
10
|
import { DapObserverUiController } from "./dap-observer-ui.js";
|
|
11
|
-
import {
|
|
11
|
+
import { createDapToolDefinition, type DapToolRuntime } from "./dap-tool.js";
|
|
12
12
|
import { resolveDapSettings } from "./pi-dap-settings.js";
|
|
13
13
|
|
|
14
|
-
/** Runtime construction effect kept narrow so lifecycle tests use an isolated Pi agent directory. */
|
|
15
|
-
export interface PiDapLifecycleEffects {
|
|
16
|
-
/** Return Pi's trust-aware global settings directory. */
|
|
17
|
-
getAgentDirectory(): string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
interface ActivePiDapSession extends DapToolRuntime {
|
|
21
15
|
readonly observer: DapObserverUiController;
|
|
22
16
|
readonly session: DapSession;
|
|
23
17
|
readonly sessionFiles: DapSessionFiles;
|
|
24
18
|
}
|
|
25
19
|
|
|
26
|
-
const productionPiDapLifecycleEffects: PiDapLifecycleEffects = {
|
|
27
|
-
getAgentDirectory: getAgentDir,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
20
|
/** Own settings, one Debug Session, one registered tool, and cleanup for a Pi conversation session. */
|
|
31
21
|
export class PiDapLifecycleController {
|
|
32
22
|
private activeSession: ActivePiDapSession | undefined;
|
|
33
23
|
private shutdownPromise: Promise<void> | undefined;
|
|
34
24
|
private toolRegistered = false;
|
|
35
25
|
|
|
36
|
-
/** Bind lifecycle handlers to Pi
|
|
26
|
+
/** Bind lifecycle handlers to Pi; `getAgentDirectory` may be overridden by tests to isolate settings. */
|
|
37
27
|
constructor(
|
|
38
28
|
private readonly pi: ExtensionAPI,
|
|
39
|
-
private readonly
|
|
29
|
+
private readonly getAgentDirectory: () => string = getAgentDir,
|
|
40
30
|
) {}
|
|
41
31
|
|
|
42
32
|
/** Register the Pi conversation session start and shutdown handlers. */
|
|
@@ -47,7 +37,7 @@ export class PiDapLifecycleController {
|
|
|
47
37
|
|
|
48
38
|
private async startSession(context: ExtensionContext): Promise<void> {
|
|
49
39
|
await this.shutdownSession();
|
|
50
|
-
const settingsManager = SettingsManager.create(context.cwd, this.
|
|
40
|
+
const settingsManager = SettingsManager.create(context.cwd, this.getAgentDirectory(), {
|
|
51
41
|
projectTrusted: context.isProjectTrusted(),
|
|
52
42
|
});
|
|
53
43
|
const settings = resolveDapSettings(settingsManager);
|
|
@@ -69,7 +59,7 @@ export class PiDapLifecycleController {
|
|
|
69
59
|
sessionFiles,
|
|
70
60
|
};
|
|
71
61
|
if (!this.toolRegistered) {
|
|
72
|
-
|
|
62
|
+
this.pi.registerTool(createDapToolDefinition(() => this.activeSession));
|
|
73
63
|
this.toolRegistered = true;
|
|
74
64
|
}
|
|
75
65
|
}
|
|
@@ -100,9 +90,9 @@ export class PiDapLifecycleController {
|
|
|
100
90
|
|
|
101
91
|
/** Compose the source-TypeScript Pi DAP extension without starting a Debug Adapter at load time. */
|
|
102
92
|
export function createPiDapExtension(
|
|
103
|
-
|
|
93
|
+
getAgentDirectory: () => string = getAgentDir,
|
|
104
94
|
): ExtensionFactory {
|
|
105
|
-
return (pi) => new PiDapLifecycleController(pi,
|
|
95
|
+
return (pi) => new PiDapLifecycleController(pi, getAgentDirectory).register();
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
const piDapExtension = createPiDapExtension();
|
package/src/pi-dap-settings.ts
CHANGED
|
@@ -109,7 +109,6 @@ type ParsedDapAdapter =
|
|
|
109
109
|
| { readonly kind: "excluded" }
|
|
110
110
|
| {
|
|
111
111
|
readonly kind: "valid";
|
|
112
|
-
readonly scope: SettingsScope;
|
|
113
112
|
readonly value: DapAdapterDefinitionWire;
|
|
114
113
|
readonly transport: DapAdapterTransport;
|
|
115
114
|
};
|
|
@@ -216,7 +215,6 @@ function parseDapAdapters(
|
|
|
216
215
|
}
|
|
217
216
|
adapters.set(id, {
|
|
218
217
|
kind: "valid",
|
|
219
|
-
scope,
|
|
220
218
|
transport: parsedTransport.transport,
|
|
221
219
|
value: adapter,
|
|
222
220
|
});
|
|
@@ -329,32 +327,22 @@ function readDapLayer(settings: DapSettingsDocumentInput, scope: SettingsScope):
|
|
|
329
327
|
};
|
|
330
328
|
}
|
|
331
329
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (entry.kind === "valid") entries.set(id, entry);
|
|
343
|
-
}
|
|
344
|
-
return new Map([...entries].sort(([left], [right]) => left.localeCompare(right)));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function mergeDapProfiles(
|
|
348
|
-
globalEntries: ReadonlyMap<string, ParsedDapProfile>,
|
|
349
|
-
projectEntries: ReadonlyMap<string, ParsedDapProfile>,
|
|
350
|
-
): ReadonlyMap<string, Extract<ParsedDapProfile, { readonly kind: "valid" }>> {
|
|
351
|
-
const entries = new Map<string, Extract<ParsedDapProfile, { readonly kind: "valid" }>>();
|
|
330
|
+
/** Merge one settings layer over another by map key, keeping only valid entries in sorted order. */
|
|
331
|
+
function mergeValidDapEntries<
|
|
332
|
+
Entry extends { readonly kind: "excluded" } | { readonly kind: "valid" },
|
|
333
|
+
>(
|
|
334
|
+
globalEntries: ReadonlyMap<string, Entry>,
|
|
335
|
+
projectEntries: ReadonlyMap<string, Entry>,
|
|
336
|
+
): ReadonlyMap<string, Extract<Entry, { readonly kind: "valid" }>> {
|
|
337
|
+
type Valid = Extract<Entry, { readonly kind: "valid" }>;
|
|
338
|
+
const isValid = (entry: Entry): entry is Valid => entry.kind === "valid";
|
|
339
|
+
const entries = new Map<string, Valid>();
|
|
352
340
|
for (const [id, entry] of globalEntries) {
|
|
353
|
-
if (entry
|
|
341
|
+
if (isValid(entry)) entries.set(id, entry);
|
|
354
342
|
}
|
|
355
343
|
for (const [id, entry] of projectEntries) {
|
|
356
344
|
entries.delete(id);
|
|
357
|
-
if (entry
|
|
345
|
+
if (isValid(entry)) entries.set(id, entry);
|
|
358
346
|
}
|
|
359
347
|
return new Map([...entries].sort(([left], [right]) => left.localeCompare(right)));
|
|
360
348
|
}
|
|
@@ -376,8 +364,8 @@ function resolveDapAdapter(
|
|
|
376
364
|
export function resolveDapSettings(reader: DapSettingsReader): ResolvedDapSettings {
|
|
377
365
|
const globalLayer = readDapLayer(reader.getGlobalSettings(), "global");
|
|
378
366
|
const projectLayer = readDapLayer(reader.getProjectSettings(), "project");
|
|
379
|
-
const mergedAdapters =
|
|
380
|
-
const mergedProfiles =
|
|
367
|
+
const mergedAdapters = mergeValidDapEntries(globalLayer.adapters, projectLayer.adapters);
|
|
368
|
+
const mergedProfiles = mergeValidDapEntries(globalLayer.profiles, projectLayer.profiles);
|
|
381
369
|
const adapters = new Map(
|
|
382
370
|
[...mergedAdapters].map(([id, adapter]) => [id, resolveDapAdapter(id, adapter)]),
|
|
383
371
|
);
|