evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -101,6 +101,8 @@ export class MessageBridge {
|
|
|
101
101
|
processOwnersProvider;
|
|
102
102
|
debouncers = new Map();
|
|
103
103
|
defaultDebounce;
|
|
104
|
+
/** Serializes session selection/renewal with inbound log admission per route. */
|
|
105
|
+
admissionLocks = new Map();
|
|
104
106
|
agentRegistry;
|
|
105
107
|
bootstrapService;
|
|
106
108
|
menuDeduper = new MenuRequestDeduper();
|
|
@@ -724,6 +726,7 @@ export class MessageBridge {
|
|
|
724
726
|
register(channelName, onMessage, sendReply, adapter, channelType) {
|
|
725
727
|
const effectiveChannelType = channelType || channelName;
|
|
726
728
|
onMessage(async (msg) => {
|
|
729
|
+
let releaseAdmission;
|
|
727
730
|
let releaseSessionIngress;
|
|
728
731
|
const releaseIngress = () => {
|
|
729
732
|
releaseSessionIngress?.();
|
|
@@ -754,6 +757,12 @@ export class MessageBridge {
|
|
|
754
757
|
const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
755
758
|
const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
|
|
756
759
|
const resolvedChannelType = msg.channelType || parsedChannelKey?.type || effectiveChannelType;
|
|
760
|
+
const admissionRoute = [
|
|
761
|
+
resolvedChannelType,
|
|
762
|
+
selfAid || '',
|
|
763
|
+
msg.channelId,
|
|
764
|
+
msg.threadId || '',
|
|
765
|
+
].join('#');
|
|
757
766
|
const trustedHandoffEcho = msg.source === 'handoff'
|
|
758
767
|
&& resolvedChannelType === 'aun'
|
|
759
768
|
&& !!selfAid
|
|
@@ -1034,6 +1043,12 @@ export class MessageBridge {
|
|
|
1034
1043
|
&& !executionPermissionOverride
|
|
1035
1044
|
&& !parseFullAccessCommand(cmdContent).matched
|
|
1036
1045
|
&& this.cmdHandler.isCommand(cmdContent);
|
|
1046
|
+
if (isCmd && /^\/new(?:\s|$)/i.test(cmdContent)) {
|
|
1047
|
+
// /new changes the active main-session boundary. Serialize it with
|
|
1048
|
+
// ordinary session admission so an in-flight renew cannot append or
|
|
1049
|
+
// enqueue against a superseded session after /new commits.
|
|
1050
|
+
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1051
|
+
}
|
|
1037
1052
|
if (isCmd) {
|
|
1038
1053
|
logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
|
|
1039
1054
|
// 命令也要记录入方向 jsonl(不创建 session,直接用 chatDirPath 计算路径)
|
|
@@ -1115,6 +1130,11 @@ export class MessageBridge {
|
|
|
1115
1130
|
// 通道找不到归属时退回到 globalConfig(一般是测试场景)
|
|
1116
1131
|
const effectiveProjectPath = owningAgent?.projectPath
|
|
1117
1132
|
?? this.defaultProjectPath;
|
|
1133
|
+
// Session renewal and inbound history append form one admission
|
|
1134
|
+
// transaction. Without this per-route single-flight, a second message
|
|
1135
|
+
// can observe the just-renewed empty session before the first message
|
|
1136
|
+
// has been appended and create another renewed session.
|
|
1137
|
+
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1118
1138
|
const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
|
|
1119
1139
|
let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity);
|
|
1120
1140
|
if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
|
|
@@ -1244,6 +1264,12 @@ export class MessageBridge {
|
|
|
1244
1264
|
}
|
|
1245
1265
|
if (!handoffCandidate)
|
|
1246
1266
|
appendMessageLog(inboundEntry.chatDir, inboundEntry.entry);
|
|
1267
|
+
// The authoritative session choice is now paired with its first
|
|
1268
|
+
// conversation record. Release before waiting for queue completion so
|
|
1269
|
+
// later group messages can still exercise the normal interrupt/FIFO
|
|
1270
|
+
// policy instead of being serialized behind the whole model turn.
|
|
1271
|
+
releaseAdmission?.();
|
|
1272
|
+
releaseAdmission = undefined;
|
|
1247
1273
|
// 6. ACK + debounce/enqueue
|
|
1248
1274
|
// ACK 在到达时立即做(每条独立 ACK),不等合并
|
|
1249
1275
|
// Interrupt 模式(单聊)→ 入队前 debounce 合并
|
|
@@ -1305,10 +1331,28 @@ export class MessageBridge {
|
|
|
1305
1331
|
logger.error(`[MessageBridge] Error in onMessage handler for ${channelName}:`, error);
|
|
1306
1332
|
}
|
|
1307
1333
|
finally {
|
|
1334
|
+
releaseAdmission?.();
|
|
1308
1335
|
releaseIngress();
|
|
1309
1336
|
}
|
|
1310
1337
|
});
|
|
1311
1338
|
}
|
|
1339
|
+
async acquireAdmission(key) {
|
|
1340
|
+
const previous = this.admissionLocks.get(key) ?? Promise.resolve();
|
|
1341
|
+
let release;
|
|
1342
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
1343
|
+
const tail = previous.then(() => current);
|
|
1344
|
+
this.admissionLocks.set(key, tail);
|
|
1345
|
+
await previous;
|
|
1346
|
+
let released = false;
|
|
1347
|
+
return () => {
|
|
1348
|
+
if (released)
|
|
1349
|
+
return;
|
|
1350
|
+
released = true;
|
|
1351
|
+
release();
|
|
1352
|
+
if (this.admissionLocks.get(key) === tail)
|
|
1353
|
+
this.admissionLocks.delete(key);
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1312
1356
|
// ── Menu Protocol ──
|
|
1313
1357
|
extractTopicName(msg) {
|
|
1314
1358
|
const raw = msg.topicName
|
|
@@ -1717,7 +1761,22 @@ export class MessageBridge {
|
|
|
1717
1761
|
if (!this.cmdHandler.isCommand(content))
|
|
1718
1762
|
return false;
|
|
1719
1763
|
logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
|
|
1720
|
-
const
|
|
1764
|
+
const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
|
|
1765
|
+
const cmdResult = await this.cmdHandler.handle(content, channel, channelId, async (_cid, text, opts, updatePayload) => {
|
|
1766
|
+
if (updatePayload && adapter?.send) {
|
|
1767
|
+
const envelope = buildEnvelope({
|
|
1768
|
+
taskId: `cmd-update-${randomBytes(5).toString('hex')}`,
|
|
1769
|
+
channel,
|
|
1770
|
+
channelId,
|
|
1771
|
+
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
1772
|
+
chatmode: 'interactive',
|
|
1773
|
+
replyContext: opts ?? replyContext,
|
|
1774
|
+
});
|
|
1775
|
+
await adapter.send(envelope, updatePayload);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
await sendReply(text, opts, updatePayload);
|
|
1779
|
+
}, userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext);
|
|
1721
1780
|
logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
|
|
1722
1781
|
if (cmdResult === undefined)
|
|
1723
1782
|
return false;
|
|
@@ -1735,7 +1794,6 @@ export class MessageBridge {
|
|
|
1735
1794
|
return true;
|
|
1736
1795
|
}
|
|
1737
1796
|
// 出站走 adapter.send 统一入口
|
|
1738
|
-
const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
|
|
1739
1797
|
let responseSessionId;
|
|
1740
1798
|
try {
|
|
1741
1799
|
const responseSession = threadId
|
|
@@ -233,6 +233,39 @@ export function messageLogPath(chatDir) {
|
|
|
233
233
|
export function hasMessageLogOperation(chatDir, operationId) {
|
|
234
234
|
return findMessageLogOperationMessageId(chatDir, operationId) !== undefined;
|
|
235
235
|
}
|
|
236
|
+
/** Find the newest completed outbound operation whose ID starts with prefix. */
|
|
237
|
+
export function findLatestMessageLogOperation(chatDir, operationPrefix) {
|
|
238
|
+
const file = messageLogPath(chatDir);
|
|
239
|
+
if (!fs.existsSync(file))
|
|
240
|
+
return undefined;
|
|
241
|
+
let latest;
|
|
242
|
+
try {
|
|
243
|
+
for (const line of fs.readFileSync(file, 'utf-8').split('\n')) {
|
|
244
|
+
if (!line.trim())
|
|
245
|
+
continue;
|
|
246
|
+
try {
|
|
247
|
+
const entry = JSON.parse(line);
|
|
248
|
+
if (entry?.dir !== 'out'
|
|
249
|
+
|| typeof entry.operationId !== 'string'
|
|
250
|
+
|| (!entry.operationId.startsWith(operationPrefix))
|
|
251
|
+
|| typeof entry.msgId !== 'string'
|
|
252
|
+
|| !entry.msgId)
|
|
253
|
+
continue;
|
|
254
|
+
const ts = typeof entry.ts === 'number' && Number.isFinite(entry.ts) ? entry.ts : 0;
|
|
255
|
+
if (!latest || ts >= latest.ts) {
|
|
256
|
+
latest = { messageId: entry.msgId, operationId: entry.operationId, ts };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// skip malformed message log lines
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
return latest;
|
|
268
|
+
}
|
|
236
269
|
export function findMessageLogOperationMessageId(chatDir, operationId) {
|
|
237
270
|
const file = messageLogPath(chatDir);
|
|
238
271
|
if (!fs.existsSync(file))
|
|
@@ -88,6 +88,17 @@ export class MessageQueue {
|
|
|
88
88
|
this.markActiveTerminal(event.sessionId, event.reason || 'interrupted');
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
|
+
subscribe.call(eventBus, 'task:started', event => {
|
|
92
|
+
if (!('sessionId' in event) || !event.sessionId)
|
|
93
|
+
return;
|
|
94
|
+
const taskEvent = event;
|
|
95
|
+
for (const [queueKey, state] of this.activeStates) {
|
|
96
|
+
if (this.matchesSession(queueKey, taskEvent.sessionId)) {
|
|
97
|
+
state.taskId = taskEvent.taskId;
|
|
98
|
+
state.generation = taskEvent.generation;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
91
102
|
subscribe.call(eventBus, 'task:completed', event => {
|
|
92
103
|
if ('sessionId' in event && event.sessionId)
|
|
93
104
|
this.markActiveTerminal(event.sessionId, 'completed');
|
|
@@ -223,6 +234,7 @@ export class MessageQueue {
|
|
|
223
234
|
}
|
|
224
235
|
const tmp = `${this.dedupPath}.tmp-${process.pid}-${Date.now()}-${this.enqueueSequence}`;
|
|
225
236
|
let fd;
|
|
237
|
+
let renamed = false;
|
|
226
238
|
try {
|
|
227
239
|
fs.mkdirSync(path.dirname(this.dedupPath), { recursive: true });
|
|
228
240
|
const content = Buffer.from(JSON.stringify({
|
|
@@ -242,20 +254,22 @@ export class MessageQueue {
|
|
|
242
254
|
fs.closeSync(fd);
|
|
243
255
|
fd = undefined;
|
|
244
256
|
fs.renameSync(tmp, this.dedupPath);
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
fs.fsyncSync(persistedFd);
|
|
248
|
-
}
|
|
249
|
-
finally {
|
|
250
|
-
fs.closeSync(persistedFd);
|
|
251
|
-
}
|
|
257
|
+
renamed = true;
|
|
252
258
|
if (process.platform !== 'win32') {
|
|
253
|
-
const directoryFd = fs.openSync(path.dirname(this.dedupPath), 'r');
|
|
254
259
|
try {
|
|
255
|
-
fs.
|
|
260
|
+
const directoryFd = fs.openSync(path.dirname(this.dedupPath), 'r');
|
|
261
|
+
try {
|
|
262
|
+
fs.fsyncSync(directoryFd);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
fs.closeSync(directoryFd);
|
|
266
|
+
}
|
|
256
267
|
}
|
|
257
|
-
|
|
258
|
-
|
|
268
|
+
catch (error) {
|
|
269
|
+
// The file contents were already fsynced before rename. A platform
|
|
270
|
+
// that cannot fsync directory metadata must not make a committed
|
|
271
|
+
// dedup claim look uncommitted to the caller.
|
|
272
|
+
logger.warn(`[Queue] Durable dedup ledger directory fsync unavailable after commit: ${error instanceof Error ? error.message : String(error)}`);
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
return true;
|
|
@@ -268,11 +282,13 @@ export class MessageQueue {
|
|
|
268
282
|
}
|
|
269
283
|
catch { }
|
|
270
284
|
}
|
|
271
|
-
|
|
272
|
-
|
|
285
|
+
if (!renamed) {
|
|
286
|
+
try {
|
|
287
|
+
fs.unlinkSync(tmp);
|
|
288
|
+
}
|
|
289
|
+
catch { }
|
|
273
290
|
}
|
|
274
|
-
|
|
275
|
-
return false;
|
|
291
|
+
return renamed;
|
|
276
292
|
}
|
|
277
293
|
}
|
|
278
294
|
hasEnqueuedMessage(sessionKey, messageId, selfAID) {
|
|
@@ -623,6 +639,8 @@ export class MessageQueue {
|
|
|
623
639
|
sessionId: sessionKey,
|
|
624
640
|
reason: 'daemon_restart',
|
|
625
641
|
agentName,
|
|
642
|
+
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
643
|
+
generation: this.activeStates.get(queueKey)?.generation,
|
|
626
644
|
causation: item.message.causation,
|
|
627
645
|
});
|
|
628
646
|
barriers.push(this.triggerInterrupt(queueKey, sessionKey, this.activeStates.get(queueKey)?.baseagent, agentName, 'daemon_restart'));
|
|
@@ -1022,6 +1040,8 @@ export class MessageQueue {
|
|
|
1022
1040
|
sessionId: sessionKey,
|
|
1023
1041
|
reason: 'new_message',
|
|
1024
1042
|
agentName: this.processingAgent.get(queueKey),
|
|
1043
|
+
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
1044
|
+
generation: this.activeStates.get(queueKey)?.generation,
|
|
1025
1045
|
causation: this.activeBatches.get(queueKey)?.message.causation,
|
|
1026
1046
|
});
|
|
1027
1047
|
if (this.interruptCallback) {
|
|
@@ -1518,6 +1538,8 @@ export class MessageQueue {
|
|
|
1518
1538
|
sessionId: sessionKey,
|
|
1519
1539
|
reason: 'recalled',
|
|
1520
1540
|
agentName,
|
|
1541
|
+
taskId: activeState?.taskId,
|
|
1542
|
+
generation: activeState?.generation,
|
|
1521
1543
|
causation: active?.message.causation,
|
|
1522
1544
|
});
|
|
1523
1545
|
const barrier = this.interruptCallback
|
|
@@ -1766,6 +1788,8 @@ export class MessageQueue {
|
|
|
1766
1788
|
sessionId: sessionKey,
|
|
1767
1789
|
reason: 'stop',
|
|
1768
1790
|
agentName: name,
|
|
1791
|
+
taskId: this.activeStates.get(queueKey)?.taskId,
|
|
1792
|
+
generation: this.activeStates.get(queueKey)?.generation,
|
|
1769
1793
|
causation: this.activeBatches.get(queueKey)?.message.causation,
|
|
1770
1794
|
});
|
|
1771
1795
|
if (this.interruptCallback) {
|
|
@@ -2038,6 +2062,8 @@ export class MessageQueue {
|
|
|
2038
2062
|
sessionId: sessionKey,
|
|
2039
2063
|
reason: 'new_message',
|
|
2040
2064
|
agentName: targetAgentName,
|
|
2065
|
+
taskId: this.activeStates.get(targetQueueKey)?.taskId,
|
|
2066
|
+
generation: this.activeStates.get(targetQueueKey)?.generation,
|
|
2041
2067
|
causation: this.activeBatches.get(targetQueueKey)?.message.causation,
|
|
2042
2068
|
});
|
|
2043
2069
|
if (this.interruptCallback) {
|