maka-agent 0.2.0-dev.76.20261009 → 0.2.0-dev.78.20261010
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 +1 -1
- package/README.zh-CN.md +3 -3
- package/dist/pi-tui-pickers.js +5 -2
- package/dist/pi-tui-runner.js +211 -33
- package/dist/runtime-host-session-driver.js +91 -13
- package/dist/session-catalog-limits.js +22 -0
- package/dist/session-driver.js +10 -0
- package/dist/tui-copy-catalog.js +15 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/bot-chat-settings.js +3 -0
- package/node_modules/@maka/core/dist/bot-events.js +1 -8
- package/node_modules/@maka/core/dist/local-memory.js +38 -1
- package/node_modules/@maka/core/dist/scheduled-task.js +14 -15
- package/node_modules/@maka/core/dist/session.js +3 -0
- package/node_modules/@maka/core/dist/text-sanitize.js +8 -3
- package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
- package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +11 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +13 -5
- package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +29 -1
- package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
- package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
- package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
- package/node_modules/@maka/runtime/dist/file-stable-write.js +15 -3
- package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
- package/node_modules/@maka/runtime/dist/model-adapter.js +20 -1
- package/node_modules/@maka/runtime/dist/network/proxy-test.js +28 -1
- package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +13 -2
- package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
- package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
- package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +216 -8
- package/node_modules/@maka/runtime/dist/sandbox-boundary-tool.js +12 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +156 -17
- package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
- package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
- package/node_modules/@maka/runtime/dist/tool-availability.js +55 -18
- package/node_modules/@maka/runtime/dist/tool-call-snapshot.js +142 -0
- package/node_modules/@maka/runtime/dist/tool-runtime.js +27 -84
- package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +12 -1
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +30 -6
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
- package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
- package/node_modules/@maka/runtime-host/dist/protocol/message.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +4 -7
- package/node_modules/@maka/runtime-host/dist/server/archive-retention-coordinator.js +81 -105
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +87 -67
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
- package/node_modules/@maka/runtime-host/dist/server/host-maintenance-guards.js +95 -0
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +18 -8
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +32 -3
- package/node_modules/@maka/runtime-host/dist/server/project-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +109 -8
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
- package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
- package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
- package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
- package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +19 -24
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +74 -19
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +6 -99
- package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +14 -126
- package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
- package/node_modules/@maka/storage/dist/git-entry.js +142 -5
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
- package/package.json +2 -2
|
@@ -61,12 +61,14 @@ export function createDefaultBotChannel(provider) {
|
|
|
61
61
|
}
|
|
62
62
|
export function createDefaultBotChatSettings() {
|
|
63
63
|
return {
|
|
64
|
+
messageHandling: 'task',
|
|
64
65
|
channels: Object.fromEntries(BOT_PROVIDERS.map((provider) => [provider, createDefaultBotChannel(provider)])),
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
export function mergeBotChatSettings(current, patch) {
|
|
68
69
|
return {
|
|
69
70
|
...current,
|
|
71
|
+
...(patch?.messageHandling ? { messageHandling: patch.messageHandling } : {}),
|
|
70
72
|
channels: {
|
|
71
73
|
...current.channels,
|
|
72
74
|
...Object.fromEntries(Object.entries(patch?.channels ?? {}).map(([provider, channelPatch]) => {
|
|
@@ -93,6 +95,7 @@ export function mergeBotChatSettings(current, patch) {
|
|
|
93
95
|
}
|
|
94
96
|
export function normalizeBotChatSettings(settings, rawSettings) {
|
|
95
97
|
return {
|
|
98
|
+
messageHandling: rawSettings?.messageHandling === 'workhub' ? 'workhub' : 'task',
|
|
96
99
|
channels: Object.fromEntries(BOT_PROVIDERS.map((provider) => {
|
|
97
100
|
const rawChannel = rawSettings?.channels?.[provider];
|
|
98
101
|
return [provider, normalizeBotChannel(provider, settings.channels[provider], rawChannel)];
|
|
@@ -130,14 +130,7 @@ export function plaintextHelpReply() {
|
|
|
130
130
|
].join('\n');
|
|
131
131
|
}
|
|
132
132
|
export function formatBotMessageForSession(message) {
|
|
133
|
-
return `[${botDisplayLabel(message.platform)}
|
|
134
|
-
}
|
|
135
|
-
function sanitizeBotUserName(value) {
|
|
136
|
-
return (value
|
|
137
|
-
.replace(/[\u0000-\u001F\u007F-\u009F]+/g, ' ')
|
|
138
|
-
.replace(/[\p{Cf}]+/gu, ' ')
|
|
139
|
-
.replace(/\s+/g, ' ')
|
|
140
|
-
.trim() || 'unknown');
|
|
133
|
+
return `[${botDisplayLabel(message.platform)}] ${message.text.trim()}`;
|
|
141
134
|
}
|
|
142
135
|
/**
|
|
143
136
|
* Non-error `BotStatus.reason` values. These states have their own UI surface,
|
|
@@ -318,8 +318,11 @@ export function findLocalMemoryEntryDraftRange(input, entryId) {
|
|
|
318
318
|
end: lineStarts[endLineIndex] ?? input.length,
|
|
319
319
|
};
|
|
320
320
|
};
|
|
321
|
+
const isCodeFenceLine = createMemoryCodeFenceTracker();
|
|
321
322
|
for (let index = 0; index < lines.length; index += 1) {
|
|
322
323
|
const line = lines[index] ?? '';
|
|
324
|
+
if (isCodeFenceLine(line))
|
|
325
|
+
continue;
|
|
323
326
|
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
324
327
|
if (heading) {
|
|
325
328
|
const matched = matchCurrent(index);
|
|
@@ -421,7 +424,12 @@ function parseLocalMemoryMarkdownRaw(input) {
|
|
|
421
424
|
}
|
|
422
425
|
current = null;
|
|
423
426
|
};
|
|
427
|
+
const isCodeFenceLine = createMemoryCodeFenceTracker();
|
|
424
428
|
for (const line of lines) {
|
|
429
|
+
if (isCodeFenceLine(line)) {
|
|
430
|
+
current?.body.push(line);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
425
433
|
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
426
434
|
if (heading) {
|
|
427
435
|
flush();
|
|
@@ -455,8 +463,29 @@ function stripPromptContent(entry) {
|
|
|
455
463
|
const { promptContent: _promptContent, ...preview } = entry;
|
|
456
464
|
return preview;
|
|
457
465
|
}
|
|
466
|
+
// All entry readers and editors share fence state so code examples cannot split entries.
|
|
467
|
+
function createMemoryCodeFenceTracker() {
|
|
468
|
+
let fence;
|
|
469
|
+
return (line) => {
|
|
470
|
+
const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
471
|
+
if (fence) {
|
|
472
|
+
if (match &&
|
|
473
|
+
match[1][0] === fence.marker &&
|
|
474
|
+
match[1].length >= fence.length &&
|
|
475
|
+
match[2].trim() === '') {
|
|
476
|
+
fence = undefined;
|
|
477
|
+
}
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (!match || (match[1][0] === '`' && match[2].includes('`')))
|
|
481
|
+
return false;
|
|
482
|
+
fence = { marker: match[1][0], length: match[1].length };
|
|
483
|
+
return true;
|
|
484
|
+
};
|
|
485
|
+
}
|
|
458
486
|
function parseMetaComment(line) {
|
|
459
|
-
|
|
487
|
+
// Four-space or tab indentation denotes a code example, not entry identity or state.
|
|
488
|
+
const match = /^ {0,3}<!--\s*maka-memory:\s*(.*?)\s*-->[ \t]*$/.exec(line);
|
|
460
489
|
if (!match)
|
|
461
490
|
return null;
|
|
462
491
|
const meta = {};
|
|
@@ -482,8 +511,11 @@ function findLocalMemoryEntrySection(input, entryId) {
|
|
|
482
511
|
const proposalId = current.meta?.proposalId;
|
|
483
512
|
return id === entryId || proposalId === entryId ? { id, ...current } : null;
|
|
484
513
|
};
|
|
514
|
+
const isCodeFenceLine = createMemoryCodeFenceTracker();
|
|
485
515
|
for (let index = 0; index < lines.length; index += 1) {
|
|
486
516
|
const line = lines[index] ?? '';
|
|
517
|
+
if (isCodeFenceLine(line))
|
|
518
|
+
continue;
|
|
487
519
|
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
488
520
|
if (heading) {
|
|
489
521
|
const matched = matchCurrent();
|
|
@@ -534,8 +566,13 @@ function findLocalMemoryEntryFullSection(input, entryId) {
|
|
|
534
566
|
},
|
|
535
567
|
};
|
|
536
568
|
};
|
|
569
|
+
const isCodeFenceLine = createMemoryCodeFenceTracker();
|
|
537
570
|
for (let index = 0; index < lines.length; index += 1) {
|
|
538
571
|
const line = lines[index] ?? '';
|
|
572
|
+
if (isCodeFenceLine(line)) {
|
|
573
|
+
current?.body.push(line);
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
539
576
|
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
540
577
|
if (heading) {
|
|
541
578
|
const matched = matchCurrent(index);
|
|
@@ -493,23 +493,22 @@ function asFiniteNumber(value) {
|
|
|
493
493
|
return value;
|
|
494
494
|
return null;
|
|
495
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Calendar recurrence uses Date's local-time rules: shift gaps forward and take
|
|
498
|
+
* the first repeated time. Rebuild from the anchor so gap shifts do not carry
|
|
499
|
+
* into later cycles; cron instead skips nonexistent local times.
|
|
500
|
+
*/
|
|
496
501
|
function nextCalendarFireAt(schedule, after) {
|
|
497
502
|
const anchor = new Date(schedule.anchorAt);
|
|
498
|
-
if (schedule.recurrence === 'daily') {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
next.setHours(anchor.getHours(), anchor.getMinutes(), anchor.getSeconds(), anchor.getMilliseconds());
|
|
508
|
-
const dayDelta = (anchor.getDay() - next.getDay() + 7) % 7;
|
|
509
|
-
next.setDate(next.getDate() + dayDelta);
|
|
510
|
-
if (next.getTime() <= after)
|
|
511
|
-
next.setDate(next.getDate() + 7);
|
|
512
|
-
return next.getTime();
|
|
503
|
+
if (schedule.recurrence === 'daily' || schedule.recurrence === 'weekly') {
|
|
504
|
+
const base = new Date(after);
|
|
505
|
+
const dayOffset = schedule.recurrence === 'daily' ? 0 : (anchor.getDay() - base.getDay() + 7) % 7;
|
|
506
|
+
const periodDays = schedule.recurrence === 'daily' ? 1 : 7;
|
|
507
|
+
// Build each occurrence from the anchor: a DST gap may normalize today's
|
|
508
|
+
// wall time, but that adjusted hour must not carry into another date.
|
|
509
|
+
const occurrence = (offset) => new Date(base.getFullYear(), base.getMonth(), base.getDate() + offset, anchor.getHours(), anchor.getMinutes(), anchor.getSeconds(), anchor.getMilliseconds()).getTime();
|
|
510
|
+
const next = occurrence(dayOffset);
|
|
511
|
+
return next > after ? next : occurrence(dayOffset + periodDays);
|
|
513
512
|
}
|
|
514
513
|
const afterDate = new Date(after);
|
|
515
514
|
for (let offset = 0; offset <= 480; offset += 1) {
|
|
@@ -467,6 +467,7 @@ const WORKHUB_DELEGATION_ASSIGNED_MESSAGE_SHAPE = defineObjectShape()([
|
|
|
467
467
|
'attachments',
|
|
468
468
|
'targetAttachments',
|
|
469
469
|
'returnResults',
|
|
470
|
+
'executionPermissionMode',
|
|
470
471
|
'create',
|
|
471
472
|
'steered',
|
|
472
473
|
'replacesActionId',
|
|
@@ -797,6 +798,8 @@ function isWorkHubCoordinationMessage(message) {
|
|
|
797
798
|
typeof message.targetSessionName === 'string' &&
|
|
798
799
|
message.targetSessionName.trim().length > 0 &&
|
|
799
800
|
(message.returnResults === undefined || message.returnResults === true) &&
|
|
801
|
+
(message.executionPermissionMode === undefined ||
|
|
802
|
+
isPermissionMode(message.executionPermissionMode)) &&
|
|
800
803
|
(message.steered === undefined || message.steered === true) &&
|
|
801
804
|
((message.schemaVersion === WORKHUB_COORDINATION_RECORD_SCHEMA_VERSION &&
|
|
802
805
|
message.replacesActionId === undefined &&
|
|
@@ -84,6 +84,11 @@ const ZERO_WIDTH_REGEX = /[\u200B-\u200D\u2060-\u2064\uFEFF]/g;
|
|
|
84
84
|
*/
|
|
85
85
|
export function sanitizeUnicodeText(text, opts) {
|
|
86
86
|
const suffix = opts.truncatedSuffix ?? '…';
|
|
87
|
+
// A negative cap is clamped to zero: without it the loop never breaks (the
|
|
88
|
+
// whole input is expanded) and the slice's negative end keeps nearly the
|
|
89
|
+
// whole string — the opposite of the "hard cap" this option promises. Same
|
|
90
|
+
// boundary contract as truncateUtf16Safe's `maxUnits <= 0` guard.
|
|
91
|
+
const maxCodePoints = Math.max(0, opts.maxCodePoints);
|
|
87
92
|
const cleaned = text
|
|
88
93
|
.normalize('NFC')
|
|
89
94
|
.replace(CONTROL_CHARS_REGEX, ' ')
|
|
@@ -95,12 +100,12 @@ export function sanitizeUnicodeText(text, opts) {
|
|
|
95
100
|
for (const point of cleaned) {
|
|
96
101
|
points.push(point);
|
|
97
102
|
// One extra point detects truncation without expanding the entire input.
|
|
98
|
-
if (
|
|
103
|
+
if (points.length > maxCodePoints)
|
|
99
104
|
break;
|
|
100
105
|
}
|
|
101
|
-
if (points.length <=
|
|
106
|
+
if (points.length <= maxCodePoints)
|
|
102
107
|
return cleaned;
|
|
103
|
-
return points.slice(0,
|
|
108
|
+
return points.slice(0, maxCodePoints).join('') + suffix;
|
|
104
109
|
}
|
|
105
110
|
/**
|
|
106
111
|
* Truncate to at most `maxUnits` UTF-16 code units without ending on an
|
|
@@ -83,6 +83,10 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
83
83
|
#tasks = new Set();
|
|
84
84
|
#tasksBySession = new Map();
|
|
85
85
|
#pendingWakeIds = new Set();
|
|
86
|
+
#sessionAttention = new Map();
|
|
87
|
+
#parkedWakeIds = new Map();
|
|
88
|
+
#activityVersions = new Map();
|
|
89
|
+
#publishedActivity = new Map();
|
|
86
90
|
#abortController = new AbortController();
|
|
87
91
|
#sessionAbortControllers = new Map();
|
|
88
92
|
#sessionWakeSuppressions = new Map();
|
|
@@ -95,18 +99,45 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
95
99
|
throw new Error('Agent graph supervisor wake attempts must be a positive safe integer');
|
|
96
100
|
}
|
|
97
101
|
}
|
|
102
|
+
/** Includes queued checkpoint delivery before a supervisor Turn is admitted. */
|
|
103
|
+
readSessionActivity(rootSessionId) {
|
|
104
|
+
if (this.#closed || this.#sessionWakesSuppressed(rootSessionId))
|
|
105
|
+
return 'idle';
|
|
106
|
+
const attention = this.#sessionAttention.get(rootSessionId);
|
|
107
|
+
if (attention === 'waiting_for_user')
|
|
108
|
+
return attention;
|
|
109
|
+
if (this.#tasksBySession.has(rootSessionId))
|
|
110
|
+
return 'running';
|
|
111
|
+
return attention ?? 'idle';
|
|
112
|
+
}
|
|
113
|
+
#publishSessionActivity(rootSessionId) {
|
|
114
|
+
const activity = this.readSessionActivity(rootSessionId);
|
|
115
|
+
if ((this.#publishedActivity.get(rootSessionId) ?? 'idle') === activity)
|
|
116
|
+
return;
|
|
117
|
+
if (activity === 'idle')
|
|
118
|
+
this.#publishedActivity.delete(rootSessionId);
|
|
119
|
+
else
|
|
120
|
+
this.#publishedActivity.set(rootSessionId, activity);
|
|
121
|
+
try {
|
|
122
|
+
this.#input.onSessionActivityChanged?.(rootSessionId);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// A catalog observer never controls wake delivery.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
98
128
|
notify(rootSessionId, result) {
|
|
99
129
|
if (this.#closed ||
|
|
100
130
|
this.#sessionWakesSuppressed(rootSessionId) ||
|
|
101
131
|
(!this.#input.shouldWake && (!result || !isAgentGraphSupervisorMilestone(result)))) {
|
|
102
132
|
return undefined;
|
|
103
133
|
}
|
|
104
|
-
return this.#runTracked(rootSessionId, async (abortSignal) => {
|
|
134
|
+
return this.#runTracked(rootSessionId, async (abortSignal, setAttention) => {
|
|
105
135
|
try {
|
|
106
|
-
await this.#wake(rootSessionId, abortSignal, result);
|
|
136
|
+
await this.#wake(rootSessionId, abortSignal, setAttention, result);
|
|
107
137
|
}
|
|
108
138
|
catch (error) {
|
|
109
139
|
if (!this.#closed && !isAbortError(error)) {
|
|
140
|
+
setAttention('blocked');
|
|
110
141
|
await notifyError(this.#input.onError, rootSessionId, error);
|
|
111
142
|
}
|
|
112
143
|
}
|
|
@@ -122,12 +153,15 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
122
153
|
notifyPermissionResponse(rootSessionId) {
|
|
123
154
|
if (this.#closed || this.#sessionWakesSuppressed(rootSessionId))
|
|
124
155
|
return undefined;
|
|
125
|
-
|
|
156
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
157
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
158
|
+
return this.#runTracked(rootSessionId, async (abortSignal, setAttention) => {
|
|
126
159
|
try {
|
|
127
|
-
await this.#settlePermissionResponse(rootSessionId, abortSignal);
|
|
160
|
+
await this.#settlePermissionResponse(rootSessionId, abortSignal, setAttention);
|
|
128
161
|
}
|
|
129
162
|
catch (error) {
|
|
130
163
|
if (!this.#closed && !isAbortError(error)) {
|
|
164
|
+
setAttention('blocked');
|
|
131
165
|
await notifyError(this.#input.onError, rootSessionId, error);
|
|
132
166
|
}
|
|
133
167
|
}
|
|
@@ -174,6 +208,11 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
174
208
|
return this.#input.activityRegistry.whenIdle(rootSessionId) !== undefined;
|
|
175
209
|
}
|
|
176
210
|
async retireSessions(rootSessionIds) {
|
|
211
|
+
for (const rootSessionId of rootSessionIds) {
|
|
212
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
213
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
214
|
+
this.#publishSessionActivity(rootSessionId);
|
|
215
|
+
}
|
|
177
216
|
return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({
|
|
178
217
|
rootSessionIds,
|
|
179
218
|
reason: 'session_retired',
|
|
@@ -184,19 +223,30 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
184
223
|
return;
|
|
185
224
|
this.#closed = true;
|
|
186
225
|
this.#abortController.abort();
|
|
226
|
+
for (const sessionId of [...this.#publishedActivity.keys()])
|
|
227
|
+
this.#publishSessionActivity(sessionId);
|
|
187
228
|
}
|
|
188
229
|
async close() {
|
|
189
230
|
this.beginDrain();
|
|
190
231
|
await this.waitForIdle();
|
|
191
232
|
this.#sessionAbortControllers.clear();
|
|
192
233
|
this.#tasksBySession.clear();
|
|
234
|
+
this.#sessionAttention.clear();
|
|
235
|
+
this.#parkedWakeIds.clear();
|
|
236
|
+
this.#activityVersions.clear();
|
|
193
237
|
}
|
|
194
|
-
async #wake(rootSessionId, abortSignal, result) {
|
|
238
|
+
async #wake(rootSessionId, abortSignal, setAttention, result) {
|
|
195
239
|
abortSignal.throwIfAborted();
|
|
196
240
|
if (!(await this.#isSessionDeliverable(rootSessionId)))
|
|
197
241
|
return;
|
|
198
242
|
const snapshot = await this.#input.readSnapshot(rootSessionId);
|
|
199
243
|
abortSignal.throwIfAborted();
|
|
244
|
+
const parkedWakeId = this.#parkedWakeIds.get(rootSessionId);
|
|
245
|
+
if (parkedWakeId &&
|
|
246
|
+
(snapshot.closed || parkedWakeId !== `${snapshot.graphId}:${snapshot.snapshotVersion}`)) {
|
|
247
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
248
|
+
setAttention('idle');
|
|
249
|
+
}
|
|
200
250
|
const wakeDecision = await this.#input.shouldWake?.(rootSessionId, result, snapshot);
|
|
201
251
|
abortSignal.throwIfAborted();
|
|
202
252
|
if (wakeDecision === false ||
|
|
@@ -217,12 +267,19 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
217
267
|
snapshotVersion: snapshot.snapshotVersion,
|
|
218
268
|
rootSessionId,
|
|
219
269
|
});
|
|
220
|
-
if (this.#closed ||
|
|
221
|
-
|
|
222
|
-
|
|
270
|
+
if (this.#closed || claimed.wake.status === 'exhausted')
|
|
271
|
+
return;
|
|
272
|
+
if (claimed.wake.status === 'delivered') {
|
|
273
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
274
|
+
setAttention('idle');
|
|
223
275
|
return;
|
|
276
|
+
}
|
|
277
|
+
if (claimed.wake.status === 'waiting_permission') {
|
|
278
|
+
this.#parkedWakeIds.set(rootSessionId, wakeId);
|
|
279
|
+
setAttention('waiting_for_user');
|
|
280
|
+
}
|
|
224
281
|
abortSignal.throwIfAborted();
|
|
225
|
-
await this.#deliverWake(claimed.wake, snapshot, abortSignal, result);
|
|
282
|
+
await this.#deliverWake(claimed.wake, snapshot, abortSignal, setAttention, result);
|
|
226
283
|
}
|
|
227
284
|
finally {
|
|
228
285
|
this.#pendingWakeIds.delete(wakeId);
|
|
@@ -232,12 +289,13 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
232
289
|
if (this.#closed || this.#pendingWakeIds.has(wake.wakeId))
|
|
233
290
|
return;
|
|
234
291
|
this.#pendingWakeIds.add(wake.wakeId);
|
|
235
|
-
void this.#runTracked(wake.rootSessionId, async (abortSignal) => {
|
|
292
|
+
void this.#runTracked(wake.rootSessionId, async (abortSignal, setAttention) => {
|
|
236
293
|
try {
|
|
237
|
-
await this.#resumeWake(wake, abortSignal);
|
|
294
|
+
await this.#resumeWake(wake, abortSignal, setAttention);
|
|
238
295
|
}
|
|
239
296
|
catch (error) {
|
|
240
297
|
if (!this.#closed && !isAbortError(error)) {
|
|
298
|
+
setAttention('blocked');
|
|
241
299
|
await notifyError(this.#input.onError, wake.rootSessionId, error);
|
|
242
300
|
}
|
|
243
301
|
}
|
|
@@ -247,19 +305,39 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
247
305
|
});
|
|
248
306
|
}
|
|
249
307
|
#runTracked(rootSessionId, operation) {
|
|
308
|
+
// Duplicate checkpoint notifications share the in-flight generation. They
|
|
309
|
+
// cannot invalidate the outcome of the attempt already holding the wake.
|
|
310
|
+
const version = (this.#activityVersions.get(rootSessionId) ?? 0) +
|
|
311
|
+
(this.#tasksBySession.has(rootSessionId) ? 0 : 1);
|
|
312
|
+
this.#activityVersions.set(rootSessionId, version);
|
|
313
|
+
if (this.#sessionAttention.get(rootSessionId) !== 'waiting_for_user') {
|
|
314
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
315
|
+
}
|
|
316
|
+
const setAttention = (activity) => {
|
|
317
|
+
if (this.#closed ||
|
|
318
|
+
this.#sessionWakesSuppressed(rootSessionId) ||
|
|
319
|
+
this.#activityVersions.get(rootSessionId) !== version)
|
|
320
|
+
return;
|
|
321
|
+
if (activity === 'idle')
|
|
322
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
323
|
+
else
|
|
324
|
+
this.#sessionAttention.set(rootSessionId, activity);
|
|
325
|
+
this.#publishSessionActivity(rootSessionId);
|
|
326
|
+
};
|
|
250
327
|
const residency = this.#input.acquireResidency?.(rootSessionId);
|
|
251
328
|
const abortSignal = this.#sessionAbortSignal(rootSessionId);
|
|
252
329
|
const task = Promise.resolve()
|
|
253
|
-
.then(() => operation(abortSignal))
|
|
330
|
+
.then(() => operation(abortSignal, setAttention))
|
|
254
331
|
.finally(() => residency?.release());
|
|
255
332
|
this.#tasks.add(task);
|
|
256
333
|
const sessionTasks = this.#tasksBySession.get(rootSessionId) ?? new Set();
|
|
257
334
|
sessionTasks.add(task);
|
|
258
335
|
this.#tasksBySession.set(rootSessionId, sessionTasks);
|
|
336
|
+
this.#publishSessionActivity(rootSessionId);
|
|
259
337
|
void task.then(() => this.#forgetTask(rootSessionId, task), () => this.#forgetTask(rootSessionId, task));
|
|
260
338
|
return task;
|
|
261
339
|
}
|
|
262
|
-
async #resumeWake(wake, abortSignal) {
|
|
340
|
+
async #resumeWake(wake, abortSignal, setAttention) {
|
|
263
341
|
abortSignal.throwIfAborted();
|
|
264
342
|
if (!(await this.#isSessionDeliverable(wake.rootSessionId))) {
|
|
265
343
|
await this.#supersedeSession(wake.rootSessionId, 'session_unavailable');
|
|
@@ -278,9 +356,9 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
278
356
|
return;
|
|
279
357
|
}
|
|
280
358
|
abortSignal.throwIfAborted();
|
|
281
|
-
await this.#deliverWake(wake, snapshot, abortSignal);
|
|
359
|
+
await this.#deliverWake(wake, snapshot, abortSignal, setAttention);
|
|
282
360
|
}
|
|
283
|
-
async #deliverWake(wake, snapshot, abortSignal, result) {
|
|
361
|
+
async #deliverWake(wake, snapshot, abortSignal, setAttention, result) {
|
|
284
362
|
if (wake.status === 'exhausted')
|
|
285
363
|
return;
|
|
286
364
|
if (wake.attemptCount >= this.#maxDeliveryAttempts) {
|
|
@@ -323,6 +401,8 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
323
401
|
}
|
|
324
402
|
return;
|
|
325
403
|
}
|
|
404
|
+
this.#parkedWakeIds.delete(wake.rootSessionId);
|
|
405
|
+
setAttention('idle');
|
|
326
406
|
if (this.#sessionWakesSuppressed(wake.rootSessionId))
|
|
327
407
|
return;
|
|
328
408
|
if (this.#closed) {
|
|
@@ -351,6 +431,8 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
351
431
|
attemptId,
|
|
352
432
|
status: 'delivered',
|
|
353
433
|
});
|
|
434
|
+
this.#parkedWakeIds.delete(wake.rootSessionId);
|
|
435
|
+
setAttention('idle');
|
|
354
436
|
return;
|
|
355
437
|
}
|
|
356
438
|
if (outcome.kind === 'superseded') {
|
|
@@ -370,6 +452,8 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
370
452
|
attemptId,
|
|
371
453
|
status: 'waiting_permission',
|
|
372
454
|
});
|
|
455
|
+
this.#parkedWakeIds.set(wake.rootSessionId, wake.wakeId);
|
|
456
|
+
setAttention('waiting_for_user');
|
|
373
457
|
return;
|
|
374
458
|
}
|
|
375
459
|
lastFailure = wakeOutcomeFailure(outcome);
|
|
@@ -501,6 +585,9 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
501
585
|
`${snapshot.graphId}:${snapshot.snapshotVersion}` === wake.wakeId);
|
|
502
586
|
}
|
|
503
587
|
#supersedeSession(rootSessionId, reason) {
|
|
588
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
589
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
590
|
+
this.#publishSessionActivity(rootSessionId);
|
|
504
591
|
return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({
|
|
505
592
|
rootSessionIds: [rootSessionId],
|
|
506
593
|
reason,
|
|
@@ -527,7 +614,7 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
527
614
|
: `host_restart:${runStatus}`);
|
|
528
615
|
return 1;
|
|
529
616
|
}
|
|
530
|
-
async #settlePermissionResponse(rootSessionId, abortSignal) {
|
|
617
|
+
async #settlePermissionResponse(rootSessionId, abortSignal, setAttention) {
|
|
531
618
|
const activity = await this.#input.activityRegistry.acquire(rootSessionId, abortSignal);
|
|
532
619
|
const retryable = [];
|
|
533
620
|
try {
|
|
@@ -548,6 +635,8 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
548
635
|
attemptId,
|
|
549
636
|
status: 'delivered',
|
|
550
637
|
});
|
|
638
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
639
|
+
setAttention('idle');
|
|
551
640
|
continue;
|
|
552
641
|
}
|
|
553
642
|
retryable.push(await this.#markRetryable(wake.graphId, wake.wakeId, attemptId, `permission_waiter_lost:${runStatus}`));
|
|
@@ -562,6 +651,10 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
562
651
|
#beginSessionWakeSuppression(rootSessionId) {
|
|
563
652
|
const count = this.#sessionWakeSuppressions.get(rootSessionId) ?? 0;
|
|
564
653
|
this.#sessionWakeSuppressions.set(rootSessionId, count + 1);
|
|
654
|
+
this.#activityVersions.set(rootSessionId, (this.#activityVersions.get(rootSessionId) ?? 0) + 1);
|
|
655
|
+
this.#sessionAttention.delete(rootSessionId);
|
|
656
|
+
this.#parkedWakeIds.delete(rootSessionId);
|
|
657
|
+
this.#publishSessionActivity(rootSessionId);
|
|
565
658
|
if (count === 0) {
|
|
566
659
|
const controller = this.#sessionAbortControllers.get(rootSessionId) ?? new AbortController();
|
|
567
660
|
this.#sessionAbortControllers.set(rootSessionId, controller);
|
|
@@ -577,6 +670,7 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
577
670
|
return;
|
|
578
671
|
}
|
|
579
672
|
this.#sessionWakeSuppressions.delete(rootSessionId);
|
|
673
|
+
this.#publishSessionActivity(rootSessionId);
|
|
580
674
|
if (!this.#tasksBySession.has(rootSessionId)) {
|
|
581
675
|
this.#sessionAbortControllers.delete(rootSessionId);
|
|
582
676
|
}
|
|
@@ -601,11 +695,15 @@ export class AgentGraphSupervisorWakeCoordinator {
|
|
|
601
695
|
this.#tasks.delete(task);
|
|
602
696
|
const sessionTasks = this.#tasksBySession.get(rootSessionId);
|
|
603
697
|
sessionTasks?.delete(task);
|
|
604
|
-
if (sessionTasks?.size === 0)
|
|
698
|
+
if (sessionTasks?.size === 0) {
|
|
605
699
|
this.#tasksBySession.delete(rootSessionId);
|
|
700
|
+
if (!this.#sessionAttention.has(rootSessionId))
|
|
701
|
+
this.#activityVersions.delete(rootSessionId);
|
|
702
|
+
}
|
|
606
703
|
if (!this.#sessionWakesSuppressed(rootSessionId) && !this.#tasksBySession.has(rootSessionId)) {
|
|
607
704
|
this.#sessionAbortControllers.delete(rootSessionId);
|
|
608
705
|
}
|
|
706
|
+
this.#publishSessionActivity(rootSessionId);
|
|
609
707
|
}
|
|
610
708
|
}
|
|
611
709
|
export function isAgentGraphSupervisorMilestone(result) {
|
|
@@ -328,6 +328,17 @@ export class AgentRun {
|
|
|
328
328
|
const runStore = this.input.runStore;
|
|
329
329
|
if (!this.input.runtimeEventStore || !runStore)
|
|
330
330
|
return;
|
|
331
|
+
// A stream may have attempted finalization after an earlier stop failed.
|
|
332
|
+
// Join that write, but let this explicit retry reuse its reserved fact if
|
|
333
|
+
// the write failed; a rejected promise is not durable terminal evidence.
|
|
334
|
+
const previousWrite = this.terminalClaim.write;
|
|
335
|
+
try {
|
|
336
|
+
await previousWrite;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
if (this.terminalClaim.write === previousWrite)
|
|
340
|
+
this.terminalClaim.write = undefined;
|
|
341
|
+
}
|
|
331
342
|
await this.flushRuntimePartialBuffer(true);
|
|
332
343
|
// The claim only fences writers inside this Run. Another owner — a Host
|
|
333
344
|
// recovery, a resumed continuation — may have sealed the ledger already,
|
|
@@ -90,6 +90,16 @@ export class AiSdkBackend {
|
|
|
90
90
|
* read back as absent.
|
|
91
91
|
*/
|
|
92
92
|
activeTurns = new Set();
|
|
93
|
+
/**
|
|
94
|
+
* Deferred-tool activations for this Session backend. `tool_search` only
|
|
95
|
+
* adds names; Turn end and compaction do not clear them. Clearing at Turn
|
|
96
|
+
* end would change the provider tool list and miss the prompt cache on the
|
|
97
|
+
* next Turn. Compaction already invalidates that cache, so clearing there
|
|
98
|
+
* would change the prefix a second time. Concurrent Runs on this backend
|
|
99
|
+
* share the map, so a search in one Run is visible on a sibling Run's next
|
|
100
|
+
* step. A rebuilt backend starts empty.
|
|
101
|
+
*/
|
|
102
|
+
sessionActiveTools = new Map();
|
|
93
103
|
compaction;
|
|
94
104
|
turnSessionState = {};
|
|
95
105
|
constructor(input) {
|
|
@@ -254,6 +264,7 @@ export class AiSdkBackend {
|
|
|
254
264
|
providerTelemetry: this.providerTelemetry,
|
|
255
265
|
compaction: this.compaction,
|
|
256
266
|
snapshotToolAvailability: () => this.snapshotToolAvailability(),
|
|
267
|
+
sessionActiveTools: this.sessionActiveTools,
|
|
257
268
|
codeCellAdmission: this.codeCellAdmission,
|
|
258
269
|
resolvedProviderOptions: this.resolvedProviderOptions,
|
|
259
270
|
session: this.turnSessionState,
|
|
@@ -37,7 +37,7 @@ import { contextDiagnosticsCompactionOf, } from './context-diagnostics.js';
|
|
|
37
37
|
import { hasBlockingReplayDiagnostics } from './ai-sdk-compaction.js';
|
|
38
38
|
import { portableApplyPatchTool } from './apply-patch-profile.js';
|
|
39
39
|
import { RunTrace } from './run-trace.js';
|
|
40
|
-
import { REQUEST_SANDBOX_BOUNDARY_TOOL_NAME, SANDBOX_BOUNDARY_DENIED_FOR_TURN, SANDBOX_BOUNDARY_FINALIZATION_PROMPT, } from './sandbox-boundary-tool.js';
|
|
40
|
+
import { REQUEST_SANDBOX_BOUNDARY_TOOL_NAME, SANDBOX_BOUNDARY_DENIED_FOR_TURN, SANDBOX_BOUNDARY_FINALIZATION_PROMPT, requiredSandboxBoundaryToolNames, } from './sandbox-boundary-tool.js';
|
|
41
41
|
import { buildRuntimeEventModelReplayPlan, appendPriorUnknownToolResponses, buildSteeringEnvelope, collectToolActivityTurnIds, compatibleProviderReasoningReplayEventIds, formatTextWithInlineRefs, inspectPriorUnknownToolOutcomes, steeringMessagesMissingFromBase, steeringModelMessage, } from './model-history.js';
|
|
42
42
|
import { toolSchemaCharsForDiagnostics, requestCompositionToolSchemas, stableHash, toolCatalogHash, } from './request-shape.js';
|
|
43
43
|
import { toolAvailabilityHash } from './tool-availability.js';
|
|
@@ -374,13 +374,15 @@ function providerRetryDelayMs(failedAttempt, retryAfterMs) {
|
|
|
374
374
|
*
|
|
375
375
|
* Each turn owns its ToolRuntime for the same reason: gating, the loop gate,
|
|
376
376
|
* the subagent and child-run limiters, durable attempts, and step admission are
|
|
377
|
-
* all per-turn facts.
|
|
377
|
+
* all per-turn facts. Deferred-tool activation is the exception: it lives on
|
|
378
|
+
* the Session backend so later Turns — and concurrent Runs on the same
|
|
379
|
+
* backend — keep the same provider tool list.
|
|
378
380
|
*/
|
|
379
381
|
export class AiSdkTurn {
|
|
380
382
|
deps;
|
|
381
383
|
request;
|
|
382
384
|
abortController = new AbortController();
|
|
383
|
-
activeTools
|
|
385
|
+
activeTools;
|
|
384
386
|
aborted = false;
|
|
385
387
|
loopStopRequested = false;
|
|
386
388
|
loopStopReason;
|
|
@@ -412,6 +414,7 @@ export class AiSdkTurn {
|
|
|
412
414
|
request.orchestration ??
|
|
413
415
|
resolveEffectiveOrchestration(deps.backend.header.orchestrationMode, undefined);
|
|
414
416
|
this.toolRuntime = deps.createToolRuntime(this);
|
|
417
|
+
this.activeTools = deps.sessionActiveTools;
|
|
415
418
|
}
|
|
416
419
|
async *run() {
|
|
417
420
|
yield* this.runWithinScope(this.request);
|
|
@@ -805,7 +808,7 @@ export class AiSdkTurn {
|
|
|
805
808
|
}
|
|
806
809
|
// --- Build the provider-visible schema set. Tool execution stays in Runtime. ---
|
|
807
810
|
// Each logical step freezes its own scoped catalog and search projection.
|
|
808
|
-
// Mutable activation
|
|
811
|
+
// Mutable activation is the Session backend map and follows contribution identity.
|
|
809
812
|
const requiredOrchestrationTools = this.orchestration.mode === 'swarm'
|
|
810
813
|
? new Set([
|
|
811
814
|
'agent_list',
|
|
@@ -834,7 +837,11 @@ export class AiSdkTurn {
|
|
|
834
837
|
if (toolMode === 'code_mode' && snapshot.hostTools.some((tool) => tool.name === 'exec')) {
|
|
835
838
|
throw new Error('Tool name "exec" is reserved for Code Mode.');
|
|
836
839
|
}
|
|
837
|
-
const
|
|
840
|
+
const requiredTools = new Set([
|
|
841
|
+
...requiredOrchestrationTools,
|
|
842
|
+
...requiredSandboxBoundaryToolNames(snapshot.hostTools),
|
|
843
|
+
]);
|
|
844
|
+
const basePlan = snapshot.runtime.prepare(this.activeTools, requiredTools);
|
|
838
845
|
const nestedTools = nestableToolSnapshot(basePlan.providerTools, basePlan.activeTools);
|
|
839
846
|
const plan = projectToolModePlan(basePlan, toolMode, codeModeExecTool);
|
|
840
847
|
const modelTools = {};
|
|
@@ -1231,6 +1238,7 @@ export class AiSdkTurn {
|
|
|
1231
1238
|
result = await this.deps.modelAdapter.startStream({
|
|
1232
1239
|
model,
|
|
1233
1240
|
messages: dispatchMessages,
|
|
1241
|
+
historyMessageCount: attemptMessages.length,
|
|
1234
1242
|
tools: modelTools,
|
|
1235
1243
|
activeTools: activeToolsForRequest,
|
|
1236
1244
|
onStreamActivity: () => requestWatchdog?.markActivity(),
|
|
@@ -22,6 +22,11 @@ import { buildProxyDispatcher } from '../network/proxy-dispatcher.js';
|
|
|
22
22
|
import { resolveActiveProxy } from '../network/active-proxy-state.js';
|
|
23
23
|
import { FETCH_PROXY_SNAPSHOT } from '../network/scoped-fetch-transport.js';
|
|
24
24
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
25
|
+
// Bounded wait for dispatcher teardown in the error path: a graceful close
|
|
26
|
+
// that never settles must not delay the rejection the caller is owed. The
|
|
27
|
+
// success path keeps its fire-and-forget close, which owns the streaming
|
|
28
|
+
// body lifecycle and is not bounded here.
|
|
29
|
+
const DISPATCHER_CLOSE_GRACE_MS = 1_000;
|
|
25
30
|
export async function proxiedFetch(input, init = {}) {
|
|
26
31
|
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
27
32
|
const proxy = resolveActiveProxy();
|
|
@@ -70,8 +75,31 @@ export async function proxiedFetch(input, init = {}) {
|
|
|
70
75
|
catch (error) {
|
|
71
76
|
if (timer)
|
|
72
77
|
clearTimeout(timer);
|
|
78
|
+
// Mirror the shared transport teardown ordering: begin dispatcher
|
|
79
|
+
// teardown before aborting so the graceful close can retire live
|
|
80
|
+
// CONNECT tunnels, with abort still cancelling pending connects. Keep
|
|
81
|
+
// the wait bounded so a stalled close cannot hold the rejection.
|
|
82
|
+
const teardown = disposeDispatcher(timedOut);
|
|
73
83
|
controller.abort(error);
|
|
74
|
-
|
|
84
|
+
let graceTimer;
|
|
85
|
+
const graceExpired = await Promise.race([
|
|
86
|
+
teardown.then(() => false),
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
graceTimer = setTimeout(() => resolve(true), DISPATCHER_CLOSE_GRACE_MS);
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
91
|
+
if (graceTimer)
|
|
92
|
+
clearTimeout(graceTimer);
|
|
93
|
+
if (graceExpired) {
|
|
94
|
+
// The graceful close never settled within the grace, so destroy the
|
|
95
|
+
// dispatcher instead of leaking it. Fire-and-forget with the error
|
|
96
|
+
// swallowed: teardown must never replace the rejection the caller is
|
|
97
|
+
// owed.
|
|
98
|
+
const disposable = dispatcher;
|
|
99
|
+
if (typeof disposable.destroy === 'function') {
|
|
100
|
+
void Promise.resolve(disposable.destroy.call(dispatcher, new Error('Dispatcher close grace expired'))).catch(() => { });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
75
103
|
throw error;
|
|
76
104
|
}
|
|
77
105
|
// Headers are in: return the original Fetch Response now. close() is
|