@jskit-ai/assistant-core 0.1.208 → 0.1.211

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.
@@ -654,6 +654,9 @@ test("native rejection and an admitted model failure retain different admission
654
654
  const f = await fixture(t);
655
655
  await assert.rejects(f.conversation.send({ messageId: "bad", text: "rejected" }), /Native request rejected/);
656
656
  assert.equal((await f.conversation.read()).status, "ready");
657
+ assert.equal((await f.conversation.inspectDelivery({ messageId: "bad" })).status, "not-sent");
658
+ assert.equal((await f.trace()).filter(item => item.url?.endsWith("/prompt_async")).length, 1);
659
+ assert.equal((await f.conversation.read()).conversationLog.length, 0);
657
660
  await f.conversation.send({ messageId: "failed", text: "failed" });
658
661
  const state = await f.conversation.wait();
659
662
  assert.equal(state.conversationLog.length, 1);
@@ -668,6 +671,8 @@ test("OpenCode refuses a different resolved account before dispatch", async t =>
668
671
  f.setKey("another-account");
669
672
  await assert.rejects(f.conversation.send({ messageId: "second", text: "Again" }), /another OpenCode account/);
670
673
  assert.equal((await f.trace()).filter(item => item.url?.endsWith("/prompt_async")).length, 1);
674
+ assert.equal((await f.conversation.inspectDelivery({ messageId: "second" })).status, "not-sent");
675
+ assert.equal((await f.trace()).filter(item => item.url?.endsWith("/prompt_async")).length, 1);
671
676
  });
672
677
 
673
678
  test("loss of the event connection fails accepted work and proves process cleanup", async t => {
@@ -906,6 +911,8 @@ test("OpenCode explicit selection recovers an older mismatched configuration wit
906
911
  engine: "opencode", configuration: { ...configuration, integrationId: "flash" } };
907
912
  await f.conversation.select(switchToFlash);
908
913
  assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 1);
914
+ assert.equal((await f.conversation.inspectDelivery({ messageId: rejected.messageId })).status, "not-sent");
915
+ assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 1);
909
916
  await f.conversation.send({ messageId: "new-authored-message", text: "Continue the same conversation" });
910
917
  const after = await f.conversation.wait();
911
918
  assert.equal(after.id, original.id);
@@ -933,6 +940,7 @@ test("OpenCode provider selection retains an uncertain predecessor without resen
933
940
  engine: "opencode", configuration: { ...configuration, integrationId: "flash" } });
934
941
  const saved = await f.storage.read("conversation", tx => tx.readMetadata());
935
942
  assert.deepEqual(saved.runtime.predecessors.at(-1).request, metadata.runtime.request);
943
+ assert.deepEqual(await f.conversation.inspectDelivery({ messageId: "unknown-old-id" }), { status: "unknown", messageId: "unknown-old-id" });
936
944
  await assert.rejects(f.conversation.send({ messageId: "unknown-old-id", text: "lost" }),
937
945
  /pending.*another|another.*pending|another native|uncertain/i);
938
946
  assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 1);
@@ -986,3 +994,349 @@ test("returning to OpenCode restores only a retained binding with the selected i
986
994
  "Returning to the same integration restores native identity after verified process cleanup");
987
995
  assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 2);
988
996
  });
997
+
998
+ for (const maxFinalReplyCharacters of [13, 12]) {
999
+ test("Opencode recovered reply enforces configured final limit of " + maxFinalReplyCharacters, async t => {
1000
+ const limits = { maxFinalReplyCharacters: 13 };
1001
+ const f = await fixture(t, { limits });
1002
+ await f.conversation.send(input);
1003
+ assert.equal((await f.conversation.wait()).conversationLog[0].assistant.text, "Answer: Hello");
1004
+ await f.first.close();
1005
+ await f.storage.write("conversation", async tx => {
1006
+ const turn = await tx.readTurn("000001");
1007
+ await tx.updateTurnMetadata(turn.turnId, { runtime: { ...turn.metadata.runtime, status: "running" } });
1008
+ await tx.replaceAssistant(turn.turnId, { ...turn.assistant, text: "" });
1009
+ });
1010
+ limits.maxFinalReplyCharacters = maxFinalReplyCharacters;
1011
+ const reopened = await f.runtime().open({ id: "conversation" });
1012
+ const receipt = await reopened.inspectDelivery({ messageId: input.messageId });
1013
+ assert.equal(receipt.status, "accepted", "A reply rejection must retain the proven user admission");
1014
+ assert.equal(receipt.recovered, true);
1015
+ const state = await reopened.read();
1016
+ const turn = state.conversationLog[0];
1017
+ assert.equal(turn.user.text, input.text);
1018
+ assert.equal(turn.metadata.runtime.status, maxFinalReplyCharacters === 13 ? "interrupted" : "failed");
1019
+ assert.equal(turn.assistant?.text, maxFinalReplyCharacters === 13 ? "Answer: Hello" : "");
1020
+ if (maxFinalReplyCharacters === 12) assert.match(state.error, /final reply limit/);
1021
+ assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 1, "Recovery cannot repeat inference");
1022
+ });
1023
+ }
1024
+
1025
+ for (const savedStatus of ["cancelled", "failed", "unknown-tool", "interrupted"]) {
1026
+ test("Opencode oversized recovery preserves " + savedStatus + " evidence", async t => {
1027
+ const limits = { maxFinalReplyCharacters: 13 };
1028
+ const f = await fixture(t, { limits });
1029
+ await f.conversation.send(input);
1030
+ assert.equal((await f.conversation.wait()).conversationLog[0].assistant.text, "Answer: Hello");
1031
+ await f.first.close();
1032
+ await f.storage.write("conversation", async tx => {
1033
+ const turn = await tx.readTurn("000001");
1034
+ await tx.replaceAssistant(turn.turnId, { ...turn.assistant, text: "" });
1035
+ await tx.updateTurnMetadata(turn.turnId, { runtime: { ...turn.metadata.runtime,
1036
+ status: savedStatus === "unknown-tool" ? "interrupted" : savedStatus,
1037
+ error: savedStatus === "unknown-tool" ? "" : "Exact prior " + savedStatus + " cause." },
1038
+ ...(savedStatus === "unknown-tool" ? { applicationTools: [{ id: "unresolved-tool", status: "unknown" }] } : {}) });
1039
+ });
1040
+ limits.maxFinalReplyCharacters = 11;
1041
+ const reopened = await f.runtime().open({ id: "conversation" });
1042
+ const receipt = await reopened.inspectDelivery({ messageId: input.messageId });
1043
+ assert.equal(receipt.status, "accepted");
1044
+ const state = await reopened.read();
1045
+ assert.equal(state.conversationLog[0].metadata.runtime.status, ["unknown-tool", "interrupted"].includes(savedStatus) ? "failed" : savedStatus);
1046
+ assert.notEqual(state.conversationLog[0].assistant?.text, "Answer: Hello");
1047
+ if (savedStatus === "unknown-tool") {
1048
+ assert.match(state.error, /application tool has no verified result.*Inspect its target/);
1049
+ assert.deepEqual(state.conversationLog[0].metadata.applicationTools, [{ id: "unresolved-tool", status: "unknown" }]);
1050
+ } else if (savedStatus === "interrupted") assert.match(state.error, /final reply limit/);
1051
+ else assert.equal(state.error, "Exact prior " + savedStatus + " cause.");
1052
+ assert.equal((await f.trace()).filter(row => row.url?.endsWith("/prompt_async")).length, 1);
1053
+ });
1054
+ }
1055
+
1056
+
1057
+ // Opt-in bound host uses the same native server, registry and Send/monitor owner.
1058
+ // The original standalone fixture above remains byte-for-byte unchanged.
1059
+ async function boundToolFixture(t) {
1060
+ const { createOpenCodeConversationServer, openCodeApplicationToolSchemas } = await import("../src/server/conversation/openCodeProcess.js");
1061
+ const { createOpenCodeSharedRuntime, ensureOpenCodeSession } = await import("../src/server/conversation/openCodeRuntime.js");
1062
+ const { observeOpenCodeEvents } = await import("../src/server/conversation/openCodeTurn.js");
1063
+ let owner;
1064
+ const handles = [];
1065
+ t.after(async () => {
1066
+ try { for (const handle of handles) await handle.dispose(); }
1067
+ finally { await owner?.stop("bound-fixture-close"); }
1068
+ });
1069
+ const f = await fixture(t);
1070
+ await f.first.close();
1071
+ const registryPath = path.join(f.directory, "bound-environments.json");
1072
+ owner = createOpenCodeSharedRuntime({ scope: f.directory });
1073
+ const connection = await f.driverOptions.connections.resolve({});
1074
+ const selected = { modelProviderId: connection.providerId, fingerprint: "bound-account" };
1075
+ const environments = new Map();
1076
+ async function open(id, execute) {
1077
+ const key = path.join(f.directory, id);
1078
+ let threadId = "";
1079
+ let lastConfiguration;
1080
+ const receipts = [];
1081
+ const start = async () => ({ connections: [selected], server: await createOpenCodeConversationServer({
1082
+ command: f.driverOptions.host.commands.opencode, workdir: f.directory, stateDirectory: f.directory,
1083
+ databasePath: path.join(f.directory, "bound-native.json"), sessionEnvironmentRegistry: registryPath,
1084
+ connection, env: f.driverOptions.host.env
1085
+ }) });
1086
+ const driver = createOpenCodeConversationDriver(f.driverOptions);
1087
+ const handle = await driver.open({ onFailure() {}, conversation: {
1088
+ publish() {}, native: { owner,
1089
+ acquire: async context => ({ key, options: context || {} }),
1090
+ preparation: {
1091
+ cleanup: () => ({ sessionId: id, options: {}, application: {
1092
+ closeTerminals: async () => ({ ok: true }), beforeRelease: async () => {},
1093
+ afterRelease: async () => {}, failure: error => { throw error; }, onRemoved: async () => { environments.delete(key); await owner.writeBindings(registryPath, key, []); }
1094
+ } }),
1095
+ interruption: async () => ({ key, threadId, async writeRun() {}, failure: error => { throw error; } }),
1096
+ async message(input, options) {
1097
+ const nativeId = `msg_bound_${input.messageId}`;
1098
+ lastConfiguration = options.applicationTools;
1099
+ return { key, input: { id: nativeId, threadId, workdir: f.directory }, application: {
1100
+ async writeRun() {},
1101
+ async prepare() {
1102
+ const target = owner.processes.get(key) || await owner.acquire(key, async () => {
1103
+ const shared = await owner.ensure(selected, start);
1104
+ return { key, sessionId: id, workdir: f.directory, upstreamSessionId: threadId, abortController: new AbortController(), server: shared.server };
1105
+ });
1106
+ const session = { selection: { agentId: "jskit-assistant-actions", modelId: "test-model", modelProviderId: "test" },
1107
+ model: { id: "test-model", providerID: "test" }, workdir: f.directory,
1108
+ invalidIdentity: () => new Error("Missing native identity"), identity: {
1109
+ write: async id => { threadId = id; },
1110
+ publish: async id => {
1111
+ environments.set(key, { upstreamSessionId: id, workdir: f.directory, modelProviderId: "test",
1112
+ conversation: { systemPrompt: "Bound original instructions", nativeTools: false, tools: options.applicationTools } });
1113
+ await owner.writeBindings(registryPath, environments, [...environments.values()]);
1114
+ }
1115
+ } };
1116
+ await ensureOpenCodeSession(target, session);
1117
+ return { target, session };
1118
+ },
1119
+ prompt: () => ({ agent: "jskit-assistant-actions", model: { id: "test-model", providerID: "test" },
1120
+ prompt: { text: input.message }, beforeDispatch: threadId => input.onPromptSending({ threadId }) }),
1121
+ commit: async admitted => { const turn = { turnId: input.messageId, messages: [{ role: "user", messageId: input.messageId, text: input.message }] }; receipts.push({ admitted, turn }); return turn; },
1122
+ monitor: {
1123
+ prepare: () => ({ fields: {} }),
1124
+ create(target, turn, _metadata, options) { return {
1125
+ observe: (_turn, signal) => observeOpenCodeEvents(target.server.client, target.upstreamSessionId,
1126
+ { signal, abortController: turn.abortController, eventStartedAt: turn.eventStartedAt }),
1127
+ eventReady: options.eventReady, finalResponse: { agent: "jskit-assistant-actions", model: { id: "test-model", providerID: "test" }, recoveryMessageId: `msg_recovery_${nativeId}` },
1128
+ async writeRun() {}, projectMessages: () => ({ failure: "", providerApiFailure: false }), completeResult: (_turn, result) => result.failure, onRetired() {}
1129
+ }; }
1130
+ }
1131
+ }, project: value => value };
1132
+ }
1133
+ }
1134
+ }
1135
+ } });
1136
+ handles.push(handle);
1137
+ function send(messageId, message, tools = true, steering = false) {
1138
+ const accepted = Promise.withResolvers(), committed = Promise.withResolvers();
1139
+ const command = { input: { messageId, nativeMessage: { messageId, message } }, context: {},
1140
+ signal: new AbortController().signal, deliveryCommitted: committed.promise,
1141
+ beforeDispatch: async identity => { assert.equal(identity.threadId, threadId); assert.ok(threadId); },
1142
+ accept: async value => { assert.equal(value.conversationTurn.turnId, messageId); accepted.resolve(value); committed.resolve(); },
1143
+ ...(tools ? { tools: { schemas: openCodeApplicationToolSchemas, maximumArgumentBytes: 32000,
1144
+ execute: async (call, options) => { assert.equal(options.messageId, messageId); return execute(call, options); } } } : {}) };
1145
+ const completion = steering ? handle.steer(command) : handle.run(command);
1146
+ return { completion, accepted: accepted.promise };
1147
+ }
1148
+ return { key, handle, send, steer: (messageId, message) => send(messageId, message, true, true), receipts, get threadId() { return threadId; }, get configuration() { return lastConfiguration; } };
1149
+ }
1150
+ return { ...f, owner, open };
1151
+ }
1152
+
1153
+ test("opted bound OpenCode reuses native tool identity and authored commit with independent shared peers", async t => {
1154
+ const f = await boundToolFixture(t);
1155
+ const calls = [];
1156
+ const a = await f.open("main-a", async (call, options) => { calls.push({ call, messageId: options.messageId }); return { result: { result: { value: 42 } } }; });
1157
+ const b = await f.open("main-b", async () => assert.fail("A peer must not receive another session's tools"));
1158
+ const peer = b.send("peer-wait", "wait", false);
1159
+ await peer.accepted;
1160
+ const first = a.send("original-words", "tools");
1161
+ await first.accepted;
1162
+ await first.completion;
1163
+ assert.equal(calls.length, 3);
1164
+ assert.deepEqual(calls.map(value => value.messageId), ["original-words", "original-words", "original-words"]);
1165
+ assert.deepEqual(calls.map(value => value.call.name), ["assistant_action_search", "assistant_action_contract", "assistant_action_execute"]);
1166
+ assert.equal(a.receipts.length, 1);
1167
+ assert.equal(a.receipts[0].turn.messages[0].text, "tools");
1168
+ assert.notEqual(a.threadId, b.threadId);
1169
+ assert.equal(f.owner.processes.size, 2);
1170
+ assert.equal(f.owner.turns.get(b.key).active, true);
1171
+ await assert.rejects(fetch(a.configuration.url, { method: "POST" }));
1172
+ await b.handle.cancel();
1173
+ await peer.completion;
1174
+ assert.equal((await f.trace()).filter(value => value.configuration).length, 1);
1175
+ });
1176
+
1177
+ test("opted bound OpenCode refuses foreign and retired tool callbacks without interrupting the current owner", async t => {
1178
+ const f = await boundToolFixture(t);
1179
+ let calls = 0;
1180
+ const main = await f.open("main", async () => { calls++; return {}; });
1181
+ const active = main.send("active-words", "wait");
1182
+ await active.accepted;
1183
+ const configuration = main.configuration;
1184
+ async function invoke(patch) {
1185
+ const response = await fetch(configuration.url, { method: "POST", headers: {
1186
+ authorization: `Bearer ${configuration.token}`, "content-type": "application/json" },
1187
+ body: JSON.stringify({ sessionId: main.threadId, messageId: "retired-assistant", id: "retired-call", name: "assistant_action_execute", input: {}, ...patch }) });
1188
+ assert.equal(response.status, 400);
1189
+ }
1190
+ await invoke({ sessionId: "foreign-session" });
1191
+ await invoke({});
1192
+ assert.equal(calls, 0);
1193
+ assert.equal(f.owner.turns.get(main.key).active, true);
1194
+ assert.equal(f.owner.turns.get(main.key).abortController.signal.aborted, false);
1195
+ await main.handle.cancel();
1196
+ await active.completion;
1197
+ });
1198
+
1199
+
1200
+ test("opted bound OpenCode owned tool failure stops its native turn and preserves the shared peer", async t => {
1201
+ const f = await boundToolFixture(t);
1202
+ const main = await f.open("failed-main", async () => { throw new Error("Owned application effect failed"); });
1203
+ const peer = await f.open("healthy-peer", async () => assert.fail("Foreign effect"));
1204
+ const waiting = peer.send("peer-active", "wait", false);
1205
+ await waiting.accepted;
1206
+ const failing = main.send("failed-effect", "tools");
1207
+ await failing.accepted;
1208
+ await assert.rejects(failing.completion, /Owned application effect failed/);
1209
+ assert.equal(f.owner.turns.get(main.key).active, false);
1210
+ assert.equal(f.owner.turns.get(peer.key).active, true);
1211
+ assert.equal(f.owner.turns.get(peer.key).abortController.signal.aborted, false);
1212
+ assert.equal(f.owner.processes.size, 2);
1213
+ await peer.handle.cancel();
1214
+ await waiting.completion;
1215
+ });
1216
+
1217
+ test("opted bound OpenCode Stop drains its invoked application action through the original native owner", async t => {
1218
+ const f = await boundToolFixture(t);
1219
+ const entered = Promise.withResolvers(), complete = Promise.withResolvers();
1220
+ t.after(() => complete.resolve({}));
1221
+ const main = await f.open("draining-main", async (_call, options) => {
1222
+ entered.resolve(options.signal);
1223
+ return complete.promise;
1224
+ });
1225
+ const running = main.send("drain-effect", "tools");
1226
+ await running.accepted;
1227
+ const signal = await entered.promise;
1228
+ let stopped = false;
1229
+ const stopping = main.handle.cancel().then(() => { stopped = true; });
1230
+ await delay(50);
1231
+ assert.equal(stopped, false);
1232
+ assert.equal(signal.aborted, true);
1233
+ complete.resolve({});
1234
+ await stopping;
1235
+ await running.completion;
1236
+ assert.equal(f.owner.turns.get(main.key).active, false);
1237
+ await assert.rejects(fetch(main.configuration.url, { method: "POST" }));
1238
+ });
1239
+
1240
+
1241
+ async function boundNativeToolRequest(f, main) {
1242
+ const target = f.owner.processes.get(main.key);
1243
+ const messages = (await target.server.client.messages(main.threadId)).data;
1244
+ const message = messages.find(row => row.type === "assistant" && row.content.some(part => part.type === "tool"));
1245
+ assert.ok(message, "The callback must match an actual retained native assistant tool row");
1246
+ const tool = message.content.find(part => part.type === "tool");
1247
+ return { target, invoke: () => fetch(main.configuration.url, { method: "POST", headers: {
1248
+ authorization: `Bearer ${main.configuration.token}`, "content-type": "application/json" },
1249
+ body: JSON.stringify({ sessionId: main.threadId, messageId: message.id, id: tool.callID,
1250
+ name: tool.tool, input: tool.state.input }) }) };
1251
+ }
1252
+
1253
+ test("opted bound OpenCode refuses an old input callback held across steering in the same active native turn", async t => {
1254
+ const f = await boundToolFixture(t);
1255
+ const effectEntered = Promise.withResolvers(), effectComplete = Promise.withResolvers();
1256
+ const readEntered = Promise.withResolvers(), readComplete = Promise.withResolvers();
1257
+ let calls = 0;
1258
+ const main = await f.open("steered-main", async () => { if (++calls === 1) { effectEntered.resolve(); return effectComplete.promise; } return {}; });
1259
+ const peer = await f.open("steered-peer", async () => assert.fail("Foreign effect"));
1260
+ const waiting = peer.send("peer-active", "wait", false);
1261
+ await waiting.accepted;
1262
+ const running = main.send("input-a", "tools");
1263
+ await running.accepted;
1264
+ await effectEntered.promise;
1265
+ const originalTurn = f.owner.turns.get(main.key);
1266
+ const { target, invoke } = await boundNativeToolRequest(f, main);
1267
+ const messages = target.server.client.messages;
1268
+ let held = false;
1269
+ try {
1270
+ target.server = { ...target.server, client: { ...target.server.client, async messages(...args) {
1271
+ const result = await messages(...args);
1272
+ if (args[1]?.limit === 100 && args[1].order === undefined && !held) {
1273
+ held = true; readEntered.resolve(); await readComplete.promise;
1274
+ }
1275
+ return result;
1276
+ } } };
1277
+ const callback = invoke();
1278
+ await readEntered.promise;
1279
+ const steered = main.steer("input-b", "wait");
1280
+ await steered.accepted;
1281
+ await steered.completion;
1282
+ assert.equal(f.owner.turns.get(main.key), originalTurn);
1283
+ assert.equal(f.owner.processes.get(main.key), target);
1284
+ assert.equal(originalTurn.inputMessageId, "msg_bound_input-b");
1285
+ readComplete.resolve();
1286
+ const response = await callback;
1287
+ assert.equal(response.status, 400);
1288
+ assert.match((await response.json()).error, /retired native turn/);
1289
+ assert.equal(calls, 1, "A stale callback must not reach the common executor");
1290
+ assert.equal(originalTurn.active, true);
1291
+ assert.equal(originalTurn.abortController.signal.aborted, false);
1292
+ assert.equal(f.owner.turns.get(peer.key).abortController.signal.aborted, false);
1293
+ assert.equal((await f.trace()).filter(row => row.url?.endsWith("/abort")).length, 0);
1294
+ } finally {
1295
+ readComplete.resolve(); effectComplete.resolve({});
1296
+ await main.handle.cancel(); await running.completion;
1297
+ await peer.handle.cancel(); await waiting.completion;
1298
+ }
1299
+ });
1300
+
1301
+ test("opted bound OpenCode does not abort newer same-turn input when an already invoked old effect fails", async t => {
1302
+ const f = await boundToolFixture(t);
1303
+ const firstEntered = Promise.withResolvers(), firstComplete = Promise.withResolvers();
1304
+ const secondEntered = Promise.withResolvers(), secondComplete = Promise.withResolvers();
1305
+ let calls = 0;
1306
+ const main = await f.open("effect-steered-main", async (_call, options) => {
1307
+ assert.equal(options.messageId, "input-a");
1308
+ if (++calls === 1) { firstEntered.resolve(); return firstComplete.promise; }
1309
+ secondEntered.resolve(); return secondComplete.promise;
1310
+ });
1311
+ const peer = await f.open("effect-steered-peer", async () => assert.fail("Foreign effect"));
1312
+ const waiting = peer.send("peer-active", "wait", false);
1313
+ await waiting.accepted;
1314
+ const running = main.send("input-a", "tools");
1315
+ await running.accepted;
1316
+ await firstEntered.promise;
1317
+ const originalTurn = f.owner.turns.get(main.key);
1318
+ const { target, invoke } = await boundNativeToolRequest(f, main);
1319
+ try {
1320
+ const callback = invoke();
1321
+ await secondEntered.promise;
1322
+ const steered = main.steer("input-b", "wait");
1323
+ await steered.accepted;
1324
+ await steered.completion;
1325
+ assert.equal(f.owner.turns.get(main.key), originalTurn);
1326
+ assert.equal(f.owner.processes.get(main.key), target);
1327
+ assert.equal(originalTurn.inputMessageId, "msg_bound_input-b");
1328
+ secondComplete.reject(new Error("Old invoked effect failed after newer accepted input"));
1329
+ const response = await callback;
1330
+ assert.equal(response.status, 400);
1331
+ assert.match((await response.json()).error, /Old invoked effect failed/);
1332
+ assert.equal(calls, 2);
1333
+ assert.equal(originalTurn.active, true);
1334
+ assert.equal(originalTurn.abortController.signal.aborted, false);
1335
+ assert.equal(f.owner.turns.get(peer.key).abortController.signal.aborted, false);
1336
+ assert.equal((await f.trace()).filter(row => row.url?.endsWith("/abort")).length, 0);
1337
+ } finally {
1338
+ secondComplete.resolve({}); firstComplete.resolve({});
1339
+ await main.handle.cancel(); await running.completion;
1340
+ await peer.handle.cancel(); await waiting.completion;
1341
+ }
1342
+ });