@sagentlab/navarch-runtime 0.1.132 → 0.1.134
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/dist/adapters/acp.cjs +183 -15
- package/dist/adapters/muse.cjs +61 -17
- package/dist/session.cjs +2 -2
- package/package.json +1 -1
package/dist/adapters/acp.cjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.acpAdapter = void 0;
|
|
4
7
|
exports.runAcpAdapter = runAcpAdapter;
|
|
8
|
+
exports.probeAcpSteering = probeAcpSteering;
|
|
5
9
|
const node_child_process_1 = require("node:child_process");
|
|
6
10
|
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
7
12
|
const node_stream_1 = require("node:stream");
|
|
8
13
|
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
9
14
|
const types_cjs_1 = require("./types.cjs");
|
|
@@ -16,6 +21,21 @@ const types_cjs_1 = require("./types.cjs");
|
|
|
16
21
|
* gets no chance to release what it is holding.
|
|
17
22
|
*/
|
|
18
23
|
const CANCEL_GRACE_MS = 2_000;
|
|
24
|
+
/** How long the steering probe waits for the agent to answer `initialize`. */
|
|
25
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
26
|
+
/**
|
|
27
|
+
* What an ACP agent that answered the probe's `initialize` with protocol v1
|
|
28
|
+
* can do: `session/cancel` and a further `session/prompt` on the same session
|
|
29
|
+
* are core v1 methods, so an interrupt stops the running prompt and starts the
|
|
30
|
+
* next one on the held connection. ACP has no input into a running prompt, so
|
|
31
|
+
* steer stays `none`, and a queued message still waits for the session loop's
|
|
32
|
+
* turn boundary.
|
|
33
|
+
*/
|
|
34
|
+
const ACP_NATIVE_STEERING = Object.freeze({
|
|
35
|
+
steer: "none",
|
|
36
|
+
queue: "boundary",
|
|
37
|
+
interrupt: "native",
|
|
38
|
+
});
|
|
19
39
|
/**
|
|
20
40
|
* Generic Agent Client Protocol v1 adapter. DeepSeek Harness is the default
|
|
21
41
|
* implementation (`dsh --profile acp`), while NAVARCH_ACP_BIN and
|
|
@@ -36,19 +56,33 @@ async function runAcpAdapter(options) {
|
|
|
36
56
|
const rawStdout = [];
|
|
37
57
|
const stderr = [];
|
|
38
58
|
const protocolInput = new node_stream_1.PassThrough();
|
|
39
|
-
|
|
59
|
+
// Agent messages of the current prompt only: after an interrupt, the report
|
|
60
|
+
// is what the last prompt produced, not the text of the turn it cut short.
|
|
61
|
+
let messages = [];
|
|
40
62
|
const thoughts = [];
|
|
41
63
|
let costUsd;
|
|
42
64
|
let timedOut = false;
|
|
43
65
|
let killedByLeaseLoss = false;
|
|
44
66
|
let sessionId;
|
|
45
67
|
let stopReason;
|
|
46
|
-
//
|
|
68
|
+
// Settles once the in-flight prompt request settles, however it settles, so
|
|
47
69
|
// cancellation can wait for the agent instead of racing the kill.
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
let promptSettled = Promise.resolve();
|
|
71
|
+
// Native interrupt (docs/navarch/live-steering.md section 5.3, row `acp`).
|
|
72
|
+
const live = options.liveInput;
|
|
73
|
+
let attached = false;
|
|
74
|
+
let promptRunning = false;
|
|
75
|
+
// This adapter sent `session/cancel` to stop the running prompt for an interrupt.
|
|
76
|
+
let interruptCancelSent = false;
|
|
77
|
+
// Interrupt inputs that wait for the running prompt to return, in the order they were sent.
|
|
78
|
+
const held = [];
|
|
79
|
+
const detach = () => {
|
|
80
|
+
if (!attached)
|
|
81
|
+
return;
|
|
82
|
+
attached = false;
|
|
83
|
+
held.length = 0;
|
|
84
|
+
live?.attach(null);
|
|
85
|
+
};
|
|
52
86
|
child.stdout.on("data", (chunk) => {
|
|
53
87
|
options.onActivity?.();
|
|
54
88
|
rawStdout.push(chunk);
|
|
@@ -86,6 +120,9 @@ async function runAcpAdapter(options) {
|
|
|
86
120
|
const controller = new AbortController();
|
|
87
121
|
let abortProtocol = Promise.resolve();
|
|
88
122
|
controller.signal.addEventListener("abort", () => {
|
|
123
|
+
// A timeout, lease loss or task cancel ends the session: no interrupt may
|
|
124
|
+
// start another prompt, and the session takes back what was not sent.
|
|
125
|
+
detach();
|
|
89
126
|
abortProtocol = (async () => {
|
|
90
127
|
if (sessionId) {
|
|
91
128
|
await connection.agent.notify(acp.methods.agent.session.cancel, { sessionId }).catch(() => undefined);
|
|
@@ -110,7 +147,7 @@ async function runAcpAdapter(options) {
|
|
|
110
147
|
onAbort();
|
|
111
148
|
try {
|
|
112
149
|
const context = connection.agent;
|
|
113
|
-
await context.request(acp.methods.agent.initialize, {
|
|
150
|
+
const initialized = await context.request(acp.methods.agent.initialize, {
|
|
114
151
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
115
152
|
clientCapabilities: {},
|
|
116
153
|
});
|
|
@@ -122,17 +159,68 @@ async function runAcpAdapter(options) {
|
|
|
122
159
|
let configOptions = created.configOptions ?? [];
|
|
123
160
|
configOptions = await setConfigOption(context, acp.methods.agent.session.setConfigOption, sessionId, configOptions, "model", options.model === "default" ? undefined : options.model);
|
|
124
161
|
await setConfigOption(context, acp.methods.agent.session.setConfigOption, sessionId, configOptions, "reasoning_effort", options.reasoningEffort);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
// The probe saw protocol v1; an agent that now answers another version
|
|
163
|
+
// takes no interrupt, which then waits for the turn boundary.
|
|
164
|
+
if (live && initialized.protocolVersion === acp.PROTOCOL_VERSION && !controller.signal.aborted) {
|
|
165
|
+
const activeSessionId = sessionId;
|
|
166
|
+
attached = true;
|
|
167
|
+
live.attach({
|
|
168
|
+
// ACP has no input into a running prompt.
|
|
169
|
+
send: () => false,
|
|
170
|
+
interrupt: (input) => {
|
|
171
|
+
if (!attached)
|
|
172
|
+
return false;
|
|
173
|
+
held.push(input);
|
|
174
|
+
if (promptRunning && !interruptCancelSent) {
|
|
175
|
+
interruptCancelSent = true;
|
|
176
|
+
void connection.agent.notify(acp.methods.agent.session.cancel, { sessionId: activeSessionId })
|
|
177
|
+
.catch(() => undefined);
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
},
|
|
130
181
|
});
|
|
131
182
|
}
|
|
132
|
-
|
|
133
|
-
|
|
183
|
+
let prompt = [{ type: "text", text: options.prompt }];
|
|
184
|
+
// The interrupt inputs `prompt` carries, and whether the first one stopped a prompt.
|
|
185
|
+
let carried = null;
|
|
186
|
+
for (;;) {
|
|
187
|
+
messages = [];
|
|
188
|
+
promptRunning = true;
|
|
189
|
+
interruptCancelSent = false;
|
|
190
|
+
const request = context.request(acp.methods.agent.session.prompt, { sessionId, prompt });
|
|
191
|
+
promptSettled = request.then(() => undefined, () => undefined);
|
|
192
|
+
// An interrupt that was waiting when the channel attached (steering hands
|
|
193
|
+
// it over at once) arrived before the first prompt ran: stop that prompt.
|
|
194
|
+
if (held.length > 0 && !interruptCancelSent) {
|
|
195
|
+
interruptCancelSent = true;
|
|
196
|
+
void context.notify(acp.methods.agent.session.cancel, { sessionId }).catch(() => undefined);
|
|
197
|
+
}
|
|
198
|
+
if (carried) {
|
|
199
|
+
// The agent reads it as the next prompt on the same session. ACP has no
|
|
200
|
+
// read receipt, and the ack cannot wait for the whole prompt, so the
|
|
201
|
+
// message counts as taken once its prompt is sent. The first input owns
|
|
202
|
+
// the stop; the rest were sent while it was pending.
|
|
203
|
+
carried.inputs.forEach((input, index) => live?.consumed(input.id, true, index === 0 && carried.stoppedTurn));
|
|
204
|
+
carried = null;
|
|
205
|
+
}
|
|
206
|
+
let response;
|
|
207
|
+
try {
|
|
208
|
+
response = await request;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
promptRunning = false;
|
|
212
|
+
}
|
|
213
|
+
stopReason = response.stopReason;
|
|
214
|
+
// A `cancelled` prompt that an interrupt asked for is not the end of the
|
|
215
|
+
// session: the interrupt's prompt follows. One that a timeout, lease loss
|
|
216
|
+
// or task cancel asked for is.
|
|
217
|
+
if (controller.signal.aborted || held.length === 0)
|
|
218
|
+
break;
|
|
219
|
+
const inputs = held.splice(0);
|
|
220
|
+
carried = { inputs, stoppedTurn: interruptCancelSent && stopReason === "cancelled" };
|
|
221
|
+
prompt = inputs.map((input) => ({ type: "text", text: input.text }));
|
|
134
222
|
}
|
|
135
|
-
|
|
223
|
+
detach();
|
|
136
224
|
await Promise.race([
|
|
137
225
|
context.request(acp.methods.agent.session.close, { sessionId }).catch(() => undefined),
|
|
138
226
|
new Promise((resolve) => setTimeout(resolve, options.timeoutMs > 0 ? Math.min(5_000, options.timeoutMs) : 5_000)),
|
|
@@ -143,6 +231,7 @@ async function runAcpAdapter(options) {
|
|
|
143
231
|
stderr.push(`\n${describeError(error)}`);
|
|
144
232
|
}
|
|
145
233
|
finally {
|
|
234
|
+
detach();
|
|
146
235
|
clearTimeout(timer);
|
|
147
236
|
options.signal?.removeEventListener("abort", onAbort);
|
|
148
237
|
await abortProtocol;
|
|
@@ -274,8 +363,87 @@ async function terminateChild(child, options, forceContainerKill) {
|
|
|
274
363
|
function describeError(error) {
|
|
275
364
|
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
276
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Probe results by command line. A pass is kept for the life of the runtime. A
|
|
368
|
+
* failure is kept for a while only, because the agent may be installed or fixed
|
|
369
|
+
* later, and probing it again for every claim would delay each session by up
|
|
370
|
+
* to the probe timeout.
|
|
371
|
+
*/
|
|
372
|
+
const acpProbeCache = new Map();
|
|
373
|
+
const PROBE_FAILURE_TTL_MS = 10 * 60_000;
|
|
374
|
+
/**
|
|
375
|
+
* Native interrupt needs an agent that speaks ACP v1, where `session/cancel`
|
|
376
|
+
* and a further `session/prompt` on the same session are core methods. The
|
|
377
|
+
* probe starts the agent, sends `initialize`, and stops it. Only the host is
|
|
378
|
+
* probed: the probe has no sandbox container to reach the Docker agent, so
|
|
379
|
+
* Docker keeps `interrupt: restart`, as does an agent that fails to start,
|
|
380
|
+
* answers late, or reports another protocol version.
|
|
381
|
+
*/
|
|
382
|
+
async function probeAcpSteering(context) {
|
|
383
|
+
if (context.sandboxMode !== "host")
|
|
384
|
+
return null;
|
|
385
|
+
const key = JSON.stringify([context.bin, ...context.extraArgs]);
|
|
386
|
+
let probe = acpProbeCache.get(key);
|
|
387
|
+
if (!probe) {
|
|
388
|
+
probe = speaksAcpV1(context).catch(() => false);
|
|
389
|
+
acpProbeCache.set(key, probe);
|
|
390
|
+
const cached = probe;
|
|
391
|
+
void cached.then((passed) => {
|
|
392
|
+
if (passed)
|
|
393
|
+
return;
|
|
394
|
+
setTimeout(() => {
|
|
395
|
+
if (acpProbeCache.get(key) === cached)
|
|
396
|
+
acpProbeCache.delete(key);
|
|
397
|
+
}, PROBE_FAILURE_TTL_MS).unref();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return (await probe) ? ACP_NATIVE_STEERING : null;
|
|
401
|
+
}
|
|
402
|
+
async function speaksAcpV1(context) {
|
|
403
|
+
const acp = await import("@agentclientprotocol/sdk");
|
|
404
|
+
const child = (0, node_child_process_1.spawn)(context.bin, context.extraArgs, {
|
|
405
|
+
detached: process.platform !== "win32",
|
|
406
|
+
// No session exists yet, so an agent that writes state at startup writes it
|
|
407
|
+
// in the temporary directory, not in the runtime's working directory.
|
|
408
|
+
cwd: node_os_1.default.tmpdir(),
|
|
409
|
+
env: { ...process.env, ...context.env },
|
|
410
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
411
|
+
});
|
|
412
|
+
const exited = new Promise((resolve) => {
|
|
413
|
+
child.once("error", () => resolve());
|
|
414
|
+
child.once("close", () => resolve());
|
|
415
|
+
});
|
|
416
|
+
child.stdin.on("error", () => undefined);
|
|
417
|
+
const connection = acp.client({ name: "navarch-runtime" }).connect(acp.ndJsonStream(node_stream_1.Writable.toWeb(child.stdin), node_stream_1.Readable.toWeb(child.stdout)));
|
|
418
|
+
let timer;
|
|
419
|
+
try {
|
|
420
|
+
return await Promise.race([
|
|
421
|
+
connection.agent.request(acp.methods.agent.initialize, {
|
|
422
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
423
|
+
clientCapabilities: {},
|
|
424
|
+
}).then((response) => response.protocolVersion === acp.PROTOCOL_VERSION, () => false),
|
|
425
|
+
exited.then(() => false),
|
|
426
|
+
new Promise((resolve) => {
|
|
427
|
+
timer = setTimeout(() => resolve(false), PROBE_TIMEOUT_MS);
|
|
428
|
+
}),
|
|
429
|
+
]);
|
|
430
|
+
}
|
|
431
|
+
finally {
|
|
432
|
+
clearTimeout(timer);
|
|
433
|
+
connection.close();
|
|
434
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
435
|
+
(0, kill_agent_cjs_1.killAgent)(child, "SIGTERM");
|
|
436
|
+
await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1_000))]);
|
|
437
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
438
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
277
442
|
exports.acpAdapter = {
|
|
278
443
|
agentType: "acp",
|
|
279
444
|
run: runAcpAdapter,
|
|
445
|
+
// The claim's baseline record. The probe upgrades it to ACP_NATIVE_STEERING
|
|
446
|
+
// on a host whose agent speaks ACP v1.
|
|
280
447
|
steering: types_cjs_1.TURN_RESTART_STEERING,
|
|
448
|
+
probeSteering: probeAcpSteering,
|
|
281
449
|
};
|
package/dist/adapters/muse.cjs
CHANGED
|
@@ -12,7 +12,11 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
14
14
|
const version_cjs_1 = require("../version.cjs");
|
|
15
|
-
|
|
15
|
+
// `interrupt: native` is MSP `turn/cancel` on the live connection, then a new
|
|
16
|
+
// `turn/start` in the same session. It is a protocol request and is not the
|
|
17
|
+
// lease-loss path, which waits CANCEL_GRACE_MS and then kills the host. There
|
|
18
|
+
// is no mid-turn input channel: `steer` is `none` and `queue` waits for the turn.
|
|
19
|
+
const MUSE_STEERING = Object.freeze({ steer: "none", queue: "boundary", interrupt: "native" });
|
|
16
20
|
const CANCEL_GRACE_MS = 2_000;
|
|
17
21
|
const SHUTDOWN_GRACE_MS = 30_000;
|
|
18
22
|
const TERMINATE_GRACE_MS = 2_000;
|
|
@@ -82,7 +86,9 @@ async function runMuseProcess(options, mcpServers, sdk) {
|
|
|
82
86
|
let protocolFailed = false;
|
|
83
87
|
const closed = deferred();
|
|
84
88
|
const stopped = deferred();
|
|
85
|
-
|
|
89
|
+
let completed = deferred();
|
|
90
|
+
let pendingInterrupt;
|
|
91
|
+
let sawTurn = false; // An earlier turn ended, so the session is resumable even if the next start fails.
|
|
86
92
|
const items = new Map();
|
|
87
93
|
const usage = new Map();
|
|
88
94
|
const pending = [];
|
|
@@ -252,19 +258,56 @@ async function runMuseProcess(options, mcpServers, sdk) {
|
|
|
252
258
|
}
|
|
253
259
|
if (timedOut || killedByLeaseLoss || protocolFailed)
|
|
254
260
|
return;
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
const startTurn = async (text) => {
|
|
262
|
+
const ack = await connection.command("turn/start", {
|
|
263
|
+
sessionId,
|
|
264
|
+
input: [{ type: "text", text }],
|
|
265
|
+
...(options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}),
|
|
266
|
+
}, { maxAttempts: 1 });
|
|
267
|
+
if (typeof ack.turnId !== "string" || ack.disposition !== "started")
|
|
268
|
+
throw new Error("Muse did not start the requested turn.");
|
|
269
|
+
turnId = ack.turnId;
|
|
270
|
+
for (const notification of pending.splice(0))
|
|
271
|
+
consume(notification);
|
|
272
|
+
};
|
|
273
|
+
const channel = {
|
|
274
|
+
send: () => false,
|
|
275
|
+
// Native interrupt: cancel the running turn on this connection. The
|
|
276
|
+
// host stays up; the run loop starts the next turn once this one ends.
|
|
277
|
+
interrupt: (input) => {
|
|
278
|
+
if (!turnId || terminal || pendingInterrupt || cancelling || timedOut || killedByLeaseLoss || protocolFailed)
|
|
279
|
+
return false;
|
|
280
|
+
pendingInterrupt = input;
|
|
281
|
+
void connection.command("turn/cancel", { sessionId, turnId }, { maxAttempts: 1 }).catch(() => undefined);
|
|
282
|
+
return true;
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
await startTurn(options.prompt);
|
|
286
|
+
options.liveInput?.attach(channel);
|
|
287
|
+
for (;;) {
|
|
288
|
+
await Promise.race([completed.promise, connection.closed]);
|
|
289
|
+
if (!terminal && !timedOut && !killedByLeaseLoss)
|
|
290
|
+
throw new Error("Muse closed before the turn completed.");
|
|
291
|
+
const next = pendingInterrupt;
|
|
292
|
+
const endedCleanly = terminal?.terminal === "cancelled" || terminal?.terminal === "completed";
|
|
293
|
+
if (!next || !endedCleanly || timedOut || killedByLeaseLoss || protocolFailed)
|
|
294
|
+
break;
|
|
295
|
+
// The cancelled turn is the interrupt, not a failure. A turn that
|
|
296
|
+
// finished before the cancel landed makes this a boundary delivery.
|
|
297
|
+
const stoppedTurn = terminal?.terminal === "cancelled";
|
|
298
|
+
pendingInterrupt = undefined;
|
|
299
|
+
sawTurn = true;
|
|
300
|
+
terminal = undefined;
|
|
301
|
+
turnId = undefined;
|
|
302
|
+
completed = deferred();
|
|
303
|
+
items.clear();
|
|
304
|
+
await startTurn(next.text);
|
|
305
|
+
// Teardown may have run while the ack was in flight; do not re-attach.
|
|
306
|
+
if (timedOut || killedByLeaseLoss || protocolFailed || cancelling)
|
|
307
|
+
return;
|
|
308
|
+
options.liveInput?.consumed(next.id, !stoppedTurn, stoppedTurn);
|
|
309
|
+
options.liveInput?.attach(channel);
|
|
310
|
+
}
|
|
268
311
|
};
|
|
269
312
|
await Promise.race([run(), stopped.promise]);
|
|
270
313
|
}
|
|
@@ -276,6 +319,7 @@ async function runMuseProcess(options, mcpServers, sdk) {
|
|
|
276
319
|
diagnostic(error instanceof Error ? error.message : "Muse protocol request failed.");
|
|
277
320
|
}
|
|
278
321
|
finally {
|
|
322
|
+
options.liveInput?.attach(null);
|
|
279
323
|
await cancelling;
|
|
280
324
|
// EOF releases Muse's writer lease. A bounded shutdown always reaps the
|
|
281
325
|
// child, including a host that ignores EOF or died during a request.
|
|
@@ -312,7 +356,7 @@ async function runMuseProcess(options, mcpServers, sdk) {
|
|
|
312
356
|
timedOut, killedByLeaseLoss, stdout, stderr,
|
|
313
357
|
...(reportText ? { reportText } : {}),
|
|
314
358
|
...(authError ? { authError } : {}),
|
|
315
|
-
...(durable && sessionId && terminal ? { agentSessionId: sessionId } : {}),
|
|
359
|
+
...(durable && sessionId && (terminal || sawTurn) ? { agentSessionId: sessionId } : {}),
|
|
316
360
|
...normalizedUsage([...usage.values()], !options.resumeSessionId),
|
|
317
361
|
};
|
|
318
362
|
}
|
|
@@ -431,4 +475,4 @@ function spawnMuse(options) {
|
|
|
431
475
|
}
|
|
432
476
|
// Read-only Navi conversations are deliberately absent: an approval mode is
|
|
433
477
|
// not a filesystem read boundary, and the SDK cannot yet prove that contract.
|
|
434
|
-
exports.museAdapter = { agentType: "muse", run: runMuseAdapter, steering:
|
|
478
|
+
exports.museAdapter = { agentType: "muse", run: runMuseAdapter, steering: MUSE_STEERING };
|
package/dist/session.cjs
CHANGED
|
@@ -914,8 +914,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
914
914
|
signal: turnAbort.signal,
|
|
915
915
|
// Native steering: the adapter keeps the agent's input open and
|
|
916
916
|
// reports each message the agent reads. Absent for every adapter
|
|
917
|
-
// whose record says `steer:
|
|
918
|
-
liveInput: steering && steeringCapabilities?.steer === "native" ? steering.liveInput : undefined,
|
|
917
|
+
// whose record says neither `steer: native` nor `interrupt: native`.
|
|
918
|
+
liveInput: steering && (steeringCapabilities?.steer === "native" || steeringCapabilities?.interrupt === "native") ? steering.liveInput : undefined,
|
|
919
919
|
onProcessStarted: () => {
|
|
920
920
|
if (steeringTaken.length > 0 && !steeringTurnStarted) {
|
|
921
921
|
clearTimeout(steeringStartTimer);
|
package/package.json
CHANGED