@github/copilot-sdk 1.0.13 → 1.0.14-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +19 -3
- package/dist/cjs/factory.js +1 -0
- package/dist/cjs/ffiRuntimeHost.js +140 -55
- package/dist/cjs/generated/rpc.js +59 -7
- package/dist/cjs/session.js +56 -8
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.js +19 -3
- package/dist/factory.d.ts +42 -16
- package/dist/factory.js +1 -0
- package/dist/ffiRuntimeHost.d.ts +8 -2
- package/dist/ffiRuntimeHost.js +140 -55
- package/dist/generated/rpc.d.ts +706 -45
- package/dist/generated/rpc.js +59 -7
- package/dist/generated/session-events.d.ts +461 -17
- package/dist/index.d.ts +2 -2
- package/dist/session.d.ts +1 -0
- package/dist/session.js +57 -9
- package/dist/types.d.ts +18 -1
- package/docs/factories.md +36 -19
- package/package.json +12 -11
package/dist/client.js
CHANGED
|
@@ -219,6 +219,8 @@ class CopilotClient {
|
|
|
219
219
|
runtimePort = null;
|
|
220
220
|
actualHost = "localhost";
|
|
221
221
|
state = "disconnected";
|
|
222
|
+
/** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
|
|
223
|
+
startPromise = null;
|
|
222
224
|
sessions = /* @__PURE__ */ new Map();
|
|
223
225
|
stderrBuffer = "";
|
|
224
226
|
// Captures CLI stderr for error messages
|
|
@@ -596,6 +598,17 @@ class CopilotClient {
|
|
|
596
598
|
if (this.state === "connected") {
|
|
597
599
|
return;
|
|
598
600
|
}
|
|
601
|
+
if (this.startPromise) {
|
|
602
|
+
return this.startPromise;
|
|
603
|
+
}
|
|
604
|
+
this.startPromise = this.doStart();
|
|
605
|
+
try {
|
|
606
|
+
await this.startPromise;
|
|
607
|
+
} finally {
|
|
608
|
+
this.startPromise = null;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async doStart() {
|
|
599
612
|
this.forceStopping = false;
|
|
600
613
|
this.connectionClosed = false;
|
|
601
614
|
this.processTransportError = null;
|
|
@@ -785,7 +798,7 @@ class CopilotClient {
|
|
|
785
798
|
const host = this.ffiHost;
|
|
786
799
|
this.ffiHost = null;
|
|
787
800
|
try {
|
|
788
|
-
host.dispose();
|
|
801
|
+
await host.dispose();
|
|
789
802
|
} catch (error) {
|
|
790
803
|
errors.push(
|
|
791
804
|
new Error(
|
|
@@ -880,11 +893,12 @@ class CopilotClient {
|
|
|
880
893
|
this.cliProcess = null;
|
|
881
894
|
}
|
|
882
895
|
if (this.ffiHost) {
|
|
896
|
+
const host = this.ffiHost;
|
|
897
|
+
this.ffiHost = null;
|
|
883
898
|
try {
|
|
884
|
-
|
|
899
|
+
await host.dispose();
|
|
885
900
|
} catch {
|
|
886
901
|
}
|
|
887
|
-
this.ffiHost = null;
|
|
888
902
|
}
|
|
889
903
|
if (this.cliStartTimeout) {
|
|
890
904
|
clearTimeout(this.cliStartTimeout);
|
|
@@ -1201,6 +1215,7 @@ class CopilotClient {
|
|
|
1201
1215
|
...this.onGitHubTelemetry != null ? { enableGitHubTelemetryForwarding: true } : {},
|
|
1202
1216
|
mcpServers: toWireMcpServers(config.mcpServers),
|
|
1203
1217
|
mcpOAuthTokenStorage: config.mcpOAuthTokenStorage,
|
|
1218
|
+
authClientIdMetadataUrl: config.authClientIdMetadataUrl,
|
|
1204
1219
|
envValueMode: "direct",
|
|
1205
1220
|
customAgents: toWireCustomAgents(config.customAgents),
|
|
1206
1221
|
customAgentsLocalOnly: config.customAgentsLocalOnly,
|
|
@@ -1446,6 +1461,7 @@ class CopilotClient {
|
|
|
1446
1461
|
...this.onGitHubTelemetry != null ? { enableGitHubTelemetryForwarding: true } : {},
|
|
1447
1462
|
mcpServers: toWireMcpServers(config.mcpServers),
|
|
1448
1463
|
mcpOAuthTokenStorage: config.mcpOAuthTokenStorage,
|
|
1464
|
+
authClientIdMetadataUrl: config.authClientIdMetadataUrl,
|
|
1449
1465
|
envValueMode: "direct",
|
|
1450
1466
|
customAgents: toWireCustomAgents(config.customAgents),
|
|
1451
1467
|
customAgentsLocalOnly: config.customAgentsLocalOnly,
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FactoryGetRunProgressRequest, FactoryListRunsRequest, FactoryListRunsResult, FactoryProgressPage, FactoryRunDetail, FactoryRunResult, FactoryRunStatus, FactoryRunSummary } from "./generated/rpc.js";
|
|
2
2
|
import type { ContextTier } from "./generated/session-events.js";
|
|
3
3
|
import type { CopilotSession } from "./session.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { FactoryMeta } from "./types.js";
|
|
5
5
|
export type { FactoryRunResult };
|
|
6
6
|
export type { FactoryAgentSummary, FactoryPhaseStatus, FactoryPhaseObservation, FactoryProgressLine, FactoryProgressPage, FactoryRunDetail, FactoryRunStatus, FactoryRunSummary, } from "./generated/rpc.js";
|
|
7
7
|
/**
|
|
@@ -81,6 +81,21 @@ export interface FactoryStepOptions {
|
|
|
81
81
|
/** Skip the journal and always invoke the producer. */
|
|
82
82
|
volatile?: boolean;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Per-invocation factory resource ceiling overrides.
|
|
86
|
+
*
|
|
87
|
+
* An omitted field preserves the existing/default ceiling, a number replaces
|
|
88
|
+
* it, and `null` explicitly makes that dimension unlimited.
|
|
89
|
+
*
|
|
90
|
+
* @experimental Part of the experimental Agent Factories surface and may
|
|
91
|
+
* change or be removed in future SDK or CLI releases.
|
|
92
|
+
*/
|
|
93
|
+
export interface FactoryLimitOverrides {
|
|
94
|
+
maxConcurrentSubagents?: number | null;
|
|
95
|
+
maxTotalSubagents?: number | null;
|
|
96
|
+
maxAiCredits?: number | null;
|
|
97
|
+
timeoutSeconds?: number | null;
|
|
98
|
+
}
|
|
84
99
|
/**
|
|
85
100
|
* One stage in a per-item factory pipeline.
|
|
86
101
|
*
|
|
@@ -101,6 +116,13 @@ export interface FactoryContext<TArgs extends JsonValue = JsonValue> {
|
|
|
101
116
|
agent(prompt: string, options?: FactoryAgentOptions): Promise<unknown>;
|
|
102
117
|
/** Memoize an arbitrary producer under a stable author-supplied key. */
|
|
103
118
|
step(key: string, producer: () => Promise<JsonValue> | JsonValue, options?: FactoryStepOptions): Promise<JsonValue>;
|
|
119
|
+
/**
|
|
120
|
+
* Pause this run at a durable, one-shot checkpoint.
|
|
121
|
+
*
|
|
122
|
+
* The first attempt to reach a key pauses and aborts cooperatively. A
|
|
123
|
+
* resumed attempt returns from the same key and continues.
|
|
124
|
+
*/
|
|
125
|
+
pause(key: string): Promise<void>;
|
|
104
126
|
/**
|
|
105
127
|
* Run thunks concurrently and await all of them.
|
|
106
128
|
*
|
|
@@ -129,7 +151,7 @@ export interface FactoryContext<TArgs extends JsonValue = JsonValue> {
|
|
|
129
151
|
args: TArgs;
|
|
130
152
|
/**
|
|
131
153
|
* The session instance returned by `joinSession`. It refuses calls that
|
|
132
|
-
* start or
|
|
154
|
+
* start, resume, or pause a factory run.
|
|
133
155
|
*/
|
|
134
156
|
session: CopilotSession;
|
|
135
157
|
/** Cooperative cancellation signal for the current factory run. */
|
|
@@ -178,7 +200,7 @@ export interface RunOptions<TArgs extends JsonValue = JsonValue> {
|
|
|
178
200
|
/** Input surfaced as `context.args`. */
|
|
179
201
|
args?: TArgs;
|
|
180
202
|
/** Optional per-invocation resource ceiling overrides. */
|
|
181
|
-
limits?:
|
|
203
|
+
limits?: FactoryLimitOverrides;
|
|
182
204
|
/** Whether to notify the originating session when the factory completes. */
|
|
183
205
|
notifyOnComplete?: boolean;
|
|
184
206
|
/** Whether to emit factory phase names to the session transcript. */
|
|
@@ -198,7 +220,7 @@ export interface RunOptions<TArgs extends JsonValue = JsonValue> {
|
|
|
198
220
|
*/
|
|
199
221
|
export interface ResumeOptions {
|
|
200
222
|
/** Optional per-invocation resource ceiling overrides. */
|
|
201
|
-
limits?:
|
|
223
|
+
limits?: FactoryLimitOverrides;
|
|
202
224
|
/** Whether to notify the originating session when the factory completes. */
|
|
203
225
|
notifyOnComplete?: boolean;
|
|
204
226
|
/** Whether to emit factory phase names to the session transcript. */
|
|
@@ -222,13 +244,14 @@ export interface SessionFactoryApi {
|
|
|
222
244
|
* Run a registered factory and resolve with its run envelope.
|
|
223
245
|
*
|
|
224
246
|
* The envelope is returned for every outcome, including `error`, `halted`,
|
|
225
|
-
* and `cancelled` — inspect `status` and read `result` only when
|
|
226
|
-
* completed.
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
247
|
+
* `paused`, and `cancelled` — inspect `status` and read `result` only when
|
|
248
|
+
* the run completed. `paused` settles the current attempt, but the same
|
|
249
|
+
* durable run can later resume under its existing run ID. SDK-initiated
|
|
250
|
+
* runs do not request permission, so they have no declined outcome. The
|
|
251
|
+
* model's `run_factory` tool requests permission before a durable row
|
|
252
|
+
* exists; declining it creates no run row. Failures that occur before a run
|
|
253
|
+
* exists (such as an unknown factory or attempting to start a run while the
|
|
254
|
+
* session is at its active top-level run limit) still reject.
|
|
232
255
|
*/
|
|
233
256
|
run(name: string, options?: RunOptions): Promise<FactoryRunResult>;
|
|
234
257
|
run<TArgs extends JsonValue>(factory: FactoryHandle<TArgs, JsonValue | void>, options?: RunOptions<TArgs>): Promise<FactoryRunResult>;
|
|
@@ -243,12 +266,13 @@ export interface SessionFactoryApi {
|
|
|
243
266
|
/** Read the latest durable envelope for a factory run. */
|
|
244
267
|
getRun(runId: string): Promise<FactoryRunResult>;
|
|
245
268
|
/**
|
|
246
|
-
* Wait for
|
|
269
|
+
* Wait for the current attempt to settle and resolve with its envelope.
|
|
247
270
|
*
|
|
248
|
-
* Resolves as soon as the run reaches `completed`, `error`, `halted`,
|
|
249
|
-
* `cancelled`, and resolves immediately when
|
|
250
|
-
*
|
|
251
|
-
*
|
|
271
|
+
* Resolves as soon as the run reaches `completed`, `error`, `halted`,
|
|
272
|
+
* `paused`, or `cancelled`, and resolves immediately when the current
|
|
273
|
+
* attempt has already settled. A `paused` envelope is an attempt-level
|
|
274
|
+
* snapshot: resuming the same durable run can later change the envelope
|
|
275
|
+
* returned by {@link SessionFactoryApi.getRun}.
|
|
252
276
|
*
|
|
253
277
|
* This watches the run's `factory.run_updated` invalidation events and
|
|
254
278
|
* periodically re-reads the durable envelope so a missed event cannot
|
|
@@ -279,6 +303,8 @@ export interface SessionFactoryApi {
|
|
|
279
303
|
getRunDetail(runId: string): Promise<FactoryRunDetail>;
|
|
280
304
|
/** Page durable progress forward, backward, or from the latest tail. */
|
|
281
305
|
getRunProgress(runId: string, options?: Omit<FactoryGetRunProgressRequest, "runId">): Promise<FactoryProgressPage>;
|
|
306
|
+
/** Pause a running factory attempt and return its `paused` envelope. */
|
|
307
|
+
pause(runId: string): Promise<FactoryRunResult>;
|
|
282
308
|
/** Cancel a factory run and return its terminal envelope. */
|
|
283
309
|
cancel(runId: string): Promise<FactoryRunResult>;
|
|
284
310
|
}
|
package/dist/factory.js
CHANGED
package/dist/ffiRuntimeHost.d.ts
CHANGED
|
@@ -4,12 +4,16 @@ export declare class FfiRuntimeHost {
|
|
|
4
4
|
private readonly cliEntrypoint;
|
|
5
5
|
private readonly environment;
|
|
6
6
|
private readonly args;
|
|
7
|
+
private static readonly quarantinedHosts;
|
|
7
8
|
private readonly lib;
|
|
8
9
|
private serverId;
|
|
9
10
|
private connectionId;
|
|
10
11
|
private disposed;
|
|
12
|
+
private starting;
|
|
11
13
|
private outboundCallback;
|
|
12
14
|
private keepAliveTimer;
|
|
15
|
+
private cleanupRetryTimer;
|
|
16
|
+
private cleanupInProgress;
|
|
13
17
|
/** The stream JSON-RPC reads server→client frames from. */
|
|
14
18
|
readonly receiveStream: PassThrough;
|
|
15
19
|
/** The stream JSON-RPC writes client→server frames to. */
|
|
@@ -30,6 +34,8 @@ export declare class FfiRuntimeHost {
|
|
|
30
34
|
*/
|
|
31
35
|
private feedInbound;
|
|
32
36
|
private unregisterCallback;
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
private scheduleCleanupRetry;
|
|
38
|
+
private tryFinalizeCleanup;
|
|
39
|
+
/** Awaits the initial cleanup attempt; a non-quiescent close is retried in the background. */
|
|
40
|
+
dispose(): Promise<void>;
|
|
35
41
|
}
|
package/dist/ffiRuntimeHost.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { PassThrough, Writable } from "node:stream";
|
|
5
5
|
const SYMBOL_PREFIX = "copilot_runtime_";
|
|
6
6
|
const KEEP_ALIVE_INTERVAL_MS = 1 << 30;
|
|
7
|
+
const CLEANUP_RETRY_INTERVAL_MS = 100;
|
|
7
8
|
let loadedLibraryPath;
|
|
8
9
|
let loadedLibrary;
|
|
9
10
|
function loadLibrary(libraryPath) {
|
|
@@ -96,12 +97,16 @@ class FfiRuntimeHost {
|
|
|
96
97
|
cliEntrypoint;
|
|
97
98
|
environment;
|
|
98
99
|
args;
|
|
100
|
+
static quarantinedHosts = /* @__PURE__ */ new Set();
|
|
99
101
|
lib;
|
|
100
102
|
serverId = 0;
|
|
101
103
|
connectionId = 0;
|
|
102
104
|
disposed = false;
|
|
105
|
+
starting = false;
|
|
103
106
|
outboundCallback;
|
|
104
107
|
keepAliveTimer;
|
|
108
|
+
cleanupRetryTimer;
|
|
109
|
+
cleanupInProgress = false;
|
|
105
110
|
/** The stream JSON-RPC reads server→client frames from. */
|
|
106
111
|
receiveStream;
|
|
107
112
|
/** The stream JSON-RPC writes client→server frames to. */
|
|
@@ -123,49 +128,65 @@ class FfiRuntimeHost {
|
|
|
123
128
|
}
|
|
124
129
|
/** Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. */
|
|
125
130
|
async start() {
|
|
131
|
+
if (this.disposed) {
|
|
132
|
+
throw new Error("The in-process runtime host is disposed.");
|
|
133
|
+
}
|
|
134
|
+
this.starting = true;
|
|
126
135
|
const argvJson = buildArgvJson(this.cliEntrypoint, this.args);
|
|
127
136
|
const envJson = buildEnvJson(this.environment);
|
|
128
|
-
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
try {
|
|
138
|
+
this.serverId = await new Promise((resolvePromise, rejectPromise) => {
|
|
139
|
+
this.lib.hostStart.async(
|
|
140
|
+
argvJson,
|
|
141
|
+
argvJson.length,
|
|
142
|
+
envJson,
|
|
143
|
+
envJson ? envJson.length : 0,
|
|
144
|
+
(error, result) => {
|
|
145
|
+
if (error) {
|
|
146
|
+
rejectPromise(error);
|
|
147
|
+
} else {
|
|
148
|
+
resolvePromise(result);
|
|
149
|
+
}
|
|
139
150
|
}
|
|
140
|
-
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
if (!this.serverId) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`copilot_runtime_host_start failed (library '${this.libraryPath}').`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (this.disposed) {
|
|
159
|
+
throw new Error("The in-process runtime host was disposed during startup.");
|
|
160
|
+
}
|
|
161
|
+
this.outboundCallback = koffi.register(
|
|
162
|
+
(_userData, bytesPtr, bytesLen) => this.feedInbound(bytesPtr, bytesLen),
|
|
163
|
+
this.lib.outboundCallbackType
|
|
141
164
|
);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
this.connectionId = this.lib.connectionOpen(
|
|
166
|
+
this.serverId,
|
|
167
|
+
this.outboundCallback,
|
|
168
|
+
null,
|
|
169
|
+
null,
|
|
170
|
+
0,
|
|
171
|
+
null,
|
|
172
|
+
0,
|
|
173
|
+
null,
|
|
174
|
+
0
|
|
175
|
+
);
|
|
176
|
+
if (!this.connectionId) {
|
|
177
|
+
this.unregisterCallback();
|
|
178
|
+
this.lib.hostShutdown(this.serverId);
|
|
179
|
+
this.serverId = 0;
|
|
180
|
+
throw new Error("copilot_runtime_connection_open failed.");
|
|
181
|
+
}
|
|
182
|
+
this.keepAliveTimer = setInterval(() => {
|
|
183
|
+
}, KEEP_ALIVE_INTERVAL_MS);
|
|
184
|
+
} finally {
|
|
185
|
+
this.starting = false;
|
|
186
|
+
if (this.disposed) {
|
|
187
|
+
void this.tryFinalizeCleanup();
|
|
188
|
+
}
|
|
166
189
|
}
|
|
167
|
-
this.keepAliveTimer = setInterval(() => {
|
|
168
|
-
}, KEEP_ALIVE_INTERVAL_MS);
|
|
169
190
|
}
|
|
170
191
|
writeFrame(frame) {
|
|
171
192
|
if (this.disposed || !this.connectionId) {
|
|
@@ -204,41 +225,105 @@ class FfiRuntimeHost {
|
|
|
204
225
|
}
|
|
205
226
|
unregisterCallback() {
|
|
206
227
|
if (this.outboundCallback === void 0) {
|
|
207
|
-
return;
|
|
228
|
+
return true;
|
|
208
229
|
}
|
|
209
230
|
const callback = this.outboundCallback;
|
|
210
|
-
this.outboundCallback = void 0;
|
|
211
231
|
try {
|
|
212
232
|
koffi.unregister(callback);
|
|
213
|
-
|
|
233
|
+
this.outboundCallback = void 0;
|
|
234
|
+
return true;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error(
|
|
237
|
+
`Failed to unregister in-process FFI callback: ${error instanceof Error ? error.stack ?? error.message : String(error)}`
|
|
238
|
+
);
|
|
239
|
+
FfiRuntimeHost.quarantinedHosts.add(this);
|
|
240
|
+
return false;
|
|
214
241
|
}
|
|
215
242
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (this.disposed) {
|
|
243
|
+
scheduleCleanupRetry() {
|
|
244
|
+
if (this.cleanupRetryTimer !== void 0) {
|
|
219
245
|
return;
|
|
220
246
|
}
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
247
|
+
this.cleanupRetryTimer = setTimeout(() => {
|
|
248
|
+
this.cleanupRetryTimer = void 0;
|
|
249
|
+
void this.tryFinalizeCleanup();
|
|
250
|
+
}, CLEANUP_RETRY_INTERVAL_MS);
|
|
251
|
+
}
|
|
252
|
+
async tryFinalizeCleanup() {
|
|
253
|
+
if (this.cleanupInProgress) {
|
|
254
|
+
this.scheduleCleanupRetry();
|
|
255
|
+
return;
|
|
225
256
|
}
|
|
257
|
+
this.cleanupInProgress = true;
|
|
226
258
|
try {
|
|
227
259
|
if (this.connectionId) {
|
|
228
|
-
|
|
260
|
+
let closed = false;
|
|
261
|
+
try {
|
|
262
|
+
closed = await new Promise((resolvePromise, rejectPromise) => {
|
|
263
|
+
this.lib.connectionClose.async(
|
|
264
|
+
this.connectionId,
|
|
265
|
+
(error, result) => {
|
|
266
|
+
if (error) {
|
|
267
|
+
rejectPromise(error);
|
|
268
|
+
} else {
|
|
269
|
+
resolvePromise(result);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(
|
|
276
|
+
`Failed to close in-process FFI connection: ${error instanceof Error ? error.stack ?? error.message : String(error)}`
|
|
277
|
+
);
|
|
278
|
+
FfiRuntimeHost.quarantinedHosts.add(this);
|
|
279
|
+
if (this.keepAliveTimer !== void 0) {
|
|
280
|
+
clearInterval(this.keepAliveTimer);
|
|
281
|
+
this.keepAliveTimer = void 0;
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (!closed) {
|
|
286
|
+
this.scheduleCleanupRetry();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
229
289
|
this.connectionId = 0;
|
|
230
290
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
const callbackUnregistered = this.unregisterCallback();
|
|
292
|
+
if (this.keepAliveTimer !== void 0) {
|
|
293
|
+
clearInterval(this.keepAliveTimer);
|
|
294
|
+
this.keepAliveTimer = void 0;
|
|
295
|
+
}
|
|
234
296
|
if (this.serverId) {
|
|
235
|
-
|
|
297
|
+
try {
|
|
298
|
+
if (!this.lib.hostShutdown(this.serverId)) {
|
|
299
|
+
console.error(
|
|
300
|
+
`In-process FFI host shutdown did not recognize server ${this.serverId}.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(
|
|
305
|
+
`Failed to shut down in-process FFI host: ${error instanceof Error ? error.stack ?? error.message : String(error)}`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
236
308
|
this.serverId = 0;
|
|
237
309
|
}
|
|
238
|
-
|
|
310
|
+
if (callbackUnregistered) {
|
|
311
|
+
FfiRuntimeHost.quarantinedHosts.delete(this);
|
|
312
|
+
}
|
|
313
|
+
} finally {
|
|
314
|
+
this.cleanupInProgress = false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** Awaits the initial cleanup attempt; a non-quiescent close is retried in the background. */
|
|
318
|
+
async dispose() {
|
|
319
|
+
if (this.disposed) {
|
|
320
|
+
return;
|
|
239
321
|
}
|
|
322
|
+
this.disposed = true;
|
|
240
323
|
this.receiveStream.end();
|
|
241
|
-
this.
|
|
324
|
+
if (!this.starting) {
|
|
325
|
+
await this.tryFinalizeCleanup();
|
|
326
|
+
}
|
|
242
327
|
}
|
|
243
328
|
}
|
|
244
329
|
export {
|