@dsh-enhanced/assistant-delivery 0.1.62 → 0.1.64
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 +32 -10
- package/bin/dsh-restart-supervisor.js +76 -0
- package/lib/agent-runtime.d.ts +21 -1
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +192 -23
- package/lib/agent-runtime.js.map +1 -1
- package/lib/coordinator.d.ts.map +1 -1
- package/lib/coordinator.js +10 -0
- package/lib/coordinator.js.map +1 -1
- package/lib/operator-snapshot.d.ts +1 -1
- package/lib/operator-snapshot.d.ts.map +1 -1
- package/lib/operator-snapshot.js +5 -5
- package/lib/operator-snapshot.js.map +1 -1
- package/lib/restart-native.d.ts +47 -0
- package/lib/restart-native.d.ts.map +1 -0
- package/lib/restart-native.js +170 -0
- package/lib/restart-native.js.map +1 -0
- package/lib/restart-runtime.d.ts +38 -0
- package/lib/restart-runtime.d.ts.map +1 -0
- package/lib/restart-runtime.js +335 -0
- package/lib/restart-runtime.js.map +1 -0
- package/lib/restart-store.d.ts +132 -0
- package/lib/restart-store.d.ts.map +1 -0
- package/lib/restart-store.js +757 -0
- package/lib/restart-store.js.map +1 -0
- package/lib/restart-supervisor.d.ts +129 -0
- package/lib/restart-supervisor.d.ts.map +1 -0
- package/lib/restart-supervisor.js +615 -0
- package/lib/restart-supervisor.js.map +1 -0
- package/lib/service.d.ts +45 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +488 -6
- package/lib/service.js.map +1 -1
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +76 -2
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +41 -1
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +433 -85
- package/lib/store.js.map +1 -1
- package/lib/tools.d.ts.map +1 -1
- package/lib/tools.js +21 -2
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +33 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +11 -10
package/lib/agent-runtime.js
CHANGED
|
@@ -13,6 +13,8 @@ import { externalPrincipalId } from './canonical.js';
|
|
|
13
13
|
import { feedbackSignalInput, feedbackUsage, parseFeedbackCommand, } from './feedback-command.js';
|
|
14
14
|
import { objectiveStateMessage } from './feedback-command.js';
|
|
15
15
|
import { learningCommandUsage, parseLearningCommand, serializeLearningExport, } from './learning-command.js';
|
|
16
|
+
import { attachRestartRun, restartDigest, restartContinuationText } from './restart-runtime.js';
|
|
17
|
+
import { admitRestartContinuation, createRestartContinuation } from './restart-native.js';
|
|
16
18
|
import { renderOwnerApprovalStatus } from './approval-status-context.js';
|
|
17
19
|
import { isExactDeliveryCommand, learningDispatchRecoveryCode, parseDeliveryCommand, permissionDispatchRecoveryCode, } from './session-commands.js';
|
|
18
20
|
import { DeliveryStoreError } from './store.js';
|
|
@@ -20,6 +22,12 @@ import { SessionLeaseUnavailable } from './session-lease-runtime.js';
|
|
|
20
22
|
import { isDeliveryGoalWakeDeadline } from './goal-wake-types.js';
|
|
21
23
|
import { parseWorkflowCommand, workflowCommandUsage, } from './workflow-command.js';
|
|
22
24
|
const SAFE_NATIVE_COMMANDS = new Set(['compact']);
|
|
25
|
+
class AgentTaskTimeoutError extends Error {
|
|
26
|
+
constructor() {
|
|
27
|
+
super('assistant-delivery: foreground task exceeded its execution deadline');
|
|
28
|
+
this.name = 'AgentTaskTimeoutError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
23
31
|
class UserSessionCancellation extends Error {
|
|
24
32
|
command;
|
|
25
33
|
constructor(command) {
|
|
@@ -1116,6 +1124,24 @@ export class DshDeliveryRuntime {
|
|
|
1116
1124
|
ctx;
|
|
1117
1125
|
policy;
|
|
1118
1126
|
options;
|
|
1127
|
+
restartRuns = new WeakMap();
|
|
1128
|
+
canRequestRestart(agent) { return this.restartRuns.get(agent)?.canRequest() === true; }
|
|
1129
|
+
requestRestart(agent, reason) {
|
|
1130
|
+
const run = this.restartRuns.get(agent);
|
|
1131
|
+
if (run === undefined)
|
|
1132
|
+
throw new Error('assistant-delivery: this foreground task has no restart authority');
|
|
1133
|
+
const record = run.request(reason);
|
|
1134
|
+
return { id: record.id, status: record.status };
|
|
1135
|
+
}
|
|
1136
|
+
async resumeRestart(record, binding, envelope, signal) {
|
|
1137
|
+
const restart = this.options.restart;
|
|
1138
|
+
if (restart === undefined || !restart.authorize(record))
|
|
1139
|
+
throw new Error('assistant-delivery: restart continuation is not authorized');
|
|
1140
|
+
return await this.process(binding, envelope, signal, undefined, () => {
|
|
1141
|
+
if (!restart.authorize(record))
|
|
1142
|
+
throw new Error('assistant-delivery: restart continuation authority changed');
|
|
1143
|
+
}, record);
|
|
1144
|
+
}
|
|
1119
1145
|
dispatchControl = 'explicit';
|
|
1120
1146
|
activeSessionControls = new Map();
|
|
1121
1147
|
sessionLeases;
|
|
@@ -1561,11 +1587,15 @@ export class DshDeliveryRuntime {
|
|
|
1561
1587
|
return true;
|
|
1562
1588
|
}
|
|
1563
1589
|
/** Wait for only the turn that claims Delivery's exact message identity. */
|
|
1564
|
-
followupTurn(agent, message, onClaimed, onSettled) {
|
|
1590
|
+
followupTurn(agent, message, onClaimed, onSettled, admit, signal) {
|
|
1565
1591
|
return new Promise((resolve, reject) => {
|
|
1566
1592
|
let claimedTurn;
|
|
1567
1593
|
let settled = false;
|
|
1594
|
+
let abortDeadline;
|
|
1568
1595
|
const cleanup = () => {
|
|
1596
|
+
if (abortDeadline !== undefined)
|
|
1597
|
+
clearTimeout(abortDeadline);
|
|
1598
|
+
signal?.removeEventListener('abort', aborted);
|
|
1569
1599
|
removeClaimed();
|
|
1570
1600
|
removeDiscarded();
|
|
1571
1601
|
removeSessionEvent();
|
|
@@ -1584,6 +1614,13 @@ export class DshDeliveryRuntime {
|
|
|
1584
1614
|
cleanup();
|
|
1585
1615
|
reject(error);
|
|
1586
1616
|
};
|
|
1617
|
+
const aborted = () => {
|
|
1618
|
+
// Let cooperative native cancellation persist its tool result and
|
|
1619
|
+
// turn/end before disposing. Uncooperative tools get only this grace.
|
|
1620
|
+
abortDeadline ??= setTimeout(() => fail(signal?.reason
|
|
1621
|
+
?? new Error('assistant-delivery: execution aborted')), 1_000);
|
|
1622
|
+
abortDeadline.unref?.();
|
|
1623
|
+
};
|
|
1587
1624
|
const removeClaimed = this.ctx.on('agent/inbox/claimed', event => {
|
|
1588
1625
|
if (event.agent === agent && event.message.id === message.id) {
|
|
1589
1626
|
claimedTurn = event.turn;
|
|
@@ -1614,8 +1651,21 @@ export class DshDeliveryRuntime {
|
|
|
1614
1651
|
settle(claimedTurn);
|
|
1615
1652
|
}
|
|
1616
1653
|
});
|
|
1654
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
1655
|
+
if (signal?.aborted) {
|
|
1656
|
+
fail(signal.reason);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1617
1659
|
try {
|
|
1618
|
-
|
|
1660
|
+
if (admit === undefined)
|
|
1661
|
+
agent.followup(message);
|
|
1662
|
+
else
|
|
1663
|
+
void admit().then(state => {
|
|
1664
|
+
if (state.kind === 'settled')
|
|
1665
|
+
settle(state.turn);
|
|
1666
|
+
else if (state.kind !== 'pending')
|
|
1667
|
+
fail(new Error('assistant-delivery: continuation was already dispatched without a settled result'));
|
|
1668
|
+
}, fail);
|
|
1619
1669
|
}
|
|
1620
1670
|
catch (error) {
|
|
1621
1671
|
fail(error);
|
|
@@ -1728,7 +1778,7 @@ export class DshDeliveryRuntime {
|
|
|
1728
1778
|
return await this.followupTurn(agent, message, onClaimed, () => {
|
|
1729
1779
|
exactTurnSettled = true;
|
|
1730
1780
|
releaseToolBarriers();
|
|
1731
|
-
});
|
|
1781
|
+
}, undefined, signal);
|
|
1732
1782
|
}
|
|
1733
1783
|
catch (error) {
|
|
1734
1784
|
if (signal.aborted)
|
|
@@ -3341,7 +3391,7 @@ export class DshDeliveryRuntime {
|
|
|
3341
3391
|
await this.disposeAfterReplyBoundary(binding.sessionId, handle);
|
|
3342
3392
|
}
|
|
3343
3393
|
}
|
|
3344
|
-
async process(binding, envelope, outerSignal, prepared, markDispatching) {
|
|
3394
|
+
async process(binding, envelope, outerSignal, prepared, markDispatching, restartContinuation) {
|
|
3345
3395
|
const sessionCommand = parseDeliveryCommand(envelope);
|
|
3346
3396
|
if (envelope.kind === 'command' && sessionCommand === undefined) {
|
|
3347
3397
|
return this.replySessionCommand(binding, envelope, {
|
|
@@ -3386,14 +3436,21 @@ export class DshDeliveryRuntime {
|
|
|
3386
3436
|
throw error;
|
|
3387
3437
|
}
|
|
3388
3438
|
let control;
|
|
3439
|
+
let taskDeadline;
|
|
3389
3440
|
try {
|
|
3390
3441
|
control = this.beginSessionControl(binding.sessionId, lease.signal);
|
|
3442
|
+
taskDeadline = setTimeout(() => {
|
|
3443
|
+
// Cancel the durable lease as well as the local wait: late tools and
|
|
3444
|
+
// model requests must remain fenced until real teardown is observed.
|
|
3445
|
+
lease.cancel(new AgentTaskTimeoutError());
|
|
3446
|
+
}, this.options.taskTimeoutMs);
|
|
3447
|
+
taskDeadline.unref?.();
|
|
3391
3448
|
lease.dispatch();
|
|
3392
3449
|
return await this.processControlled(binding, envelope, control.controller.signal, sessionCommand, prepared, markDispatching === undefined ? undefined : () => {
|
|
3393
3450
|
lease.assert();
|
|
3394
3451
|
markDispatching();
|
|
3395
3452
|
lease.assert();
|
|
3396
|
-
});
|
|
3453
|
+
}, restartContinuation);
|
|
3397
3454
|
}
|
|
3398
3455
|
catch (error) {
|
|
3399
3456
|
if (control?.command !== undefined && causedByUserCancellation(error)) {
|
|
@@ -3408,12 +3465,14 @@ export class DshDeliveryRuntime {
|
|
|
3408
3465
|
throw error;
|
|
3409
3466
|
}
|
|
3410
3467
|
finally {
|
|
3468
|
+
if (taskDeadline !== undefined)
|
|
3469
|
+
clearTimeout(taskDeadline);
|
|
3411
3470
|
if (control !== undefined)
|
|
3412
3471
|
this.endSessionControl(binding.sessionId, control);
|
|
3413
3472
|
lease.close();
|
|
3414
3473
|
}
|
|
3415
3474
|
}
|
|
3416
|
-
async processControlled(binding, envelope, signal, sessionCommand, prepared, markDispatching) {
|
|
3475
|
+
async processControlled(binding, envelope, signal, sessionCommand, prepared, markDispatching, restartContinuation) {
|
|
3417
3476
|
if (sessionCommand?.name === 'feedback') {
|
|
3418
3477
|
return await this.runFeedbackCommand(sessionCommand, binding, envelope, signal, markDispatching);
|
|
3419
3478
|
}
|
|
@@ -3516,9 +3575,13 @@ export class DshDeliveryRuntime {
|
|
|
3516
3575
|
if (agents === undefined || sessions === undefined) {
|
|
3517
3576
|
return { outcome: 'not-processed', failureCode: 'agent-runtime-unavailable', retryable: true };
|
|
3518
3577
|
}
|
|
3519
|
-
const
|
|
3578
|
+
const restart = this.options.restart;
|
|
3579
|
+
const continuationTask = restartContinuation === undefined ? undefined : restart?.store.getTask(restartContinuation.inboxId);
|
|
3580
|
+
if (restartContinuation !== undefined && continuationTask?.model === undefined)
|
|
3581
|
+
throw new Error('assistant-delivery: restart model is not frozen');
|
|
3582
|
+
const persistedSelection = restartContinuation === undefined ? this.options.getModelSelection(envelope.conversation) : undefined;
|
|
3520
3583
|
const requestedRoute = toModelRoute(persistedSelection
|
|
3521
|
-
?? prepared?.modelRoute
|
|
3584
|
+
?? continuationTask?.model ?? prepared?.modelRoute
|
|
3522
3585
|
?? { provider: this.options.provider, model: this.options.model });
|
|
3523
3586
|
const resolvedRoute = await this.resolveExecutionRoute(envelope.conversation, persistedSelection, requestedRoute, signal);
|
|
3524
3587
|
if ('retry' in resolvedRoute) {
|
|
@@ -3532,21 +3595,45 @@ export class DshDeliveryRuntime {
|
|
|
3532
3595
|
let foregroundExecution;
|
|
3533
3596
|
let removeAcceptanceRequest;
|
|
3534
3597
|
let acceptanceSucceeded = false;
|
|
3598
|
+
let completionPending = false;
|
|
3535
3599
|
let goalContinuation;
|
|
3536
3600
|
let goalContinuationQuiescent = true;
|
|
3537
3601
|
let removeAbort;
|
|
3538
3602
|
let removeProgress;
|
|
3539
3603
|
let progressOpen = true;
|
|
3604
|
+
let restartRun;
|
|
3605
|
+
let restartCheckpoint;
|
|
3540
3606
|
let progressQueue = Promise.resolve();
|
|
3607
|
+
let progressTerminal = false;
|
|
3608
|
+
const drainProgress = async () => {
|
|
3609
|
+
let timeout;
|
|
3610
|
+
try {
|
|
3611
|
+
await Promise.race([progressQueue, new Promise(resolve => {
|
|
3612
|
+
timeout = setTimeout(resolve, 5_000);
|
|
3613
|
+
timeout.unref?.();
|
|
3614
|
+
})]);
|
|
3615
|
+
}
|
|
3616
|
+
finally {
|
|
3617
|
+
if (timeout !== undefined)
|
|
3618
|
+
clearTimeout(timeout);
|
|
3619
|
+
}
|
|
3620
|
+
};
|
|
3541
3621
|
const publishProgress = (update) => {
|
|
3542
|
-
|
|
3622
|
+
const terminal = update.kind === 'completed' || update.kind === 'failed' || update.kind === 'paused';
|
|
3623
|
+
if ((!progressOpen && !terminal) || progressTerminal)
|
|
3543
3624
|
return;
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3625
|
+
if (terminal)
|
|
3626
|
+
progressTerminal = true;
|
|
3627
|
+
const publish = async () => {
|
|
3628
|
+
if ((!terminal && progressTerminal) || checkAuthorization() !== 'authorized')
|
|
3547
3629
|
return;
|
|
3548
3630
|
await this.options.progress(binding, envelope.eventId, update);
|
|
3549
|
-
}
|
|
3631
|
+
};
|
|
3632
|
+
// Terminal journaling must not wait behind a slow provider's step updates.
|
|
3633
|
+
// The adapter preserves provider ordering after staging the durable result.
|
|
3634
|
+
progressQueue = (terminal
|
|
3635
|
+
? Promise.all([progressQueue, publish()]).then(() => { })
|
|
3636
|
+
: progressQueue.then(publish))
|
|
3550
3637
|
// Progress is presentation-only: its provider failure must never retry an Agent turn.
|
|
3551
3638
|
.catch(() => { });
|
|
3552
3639
|
};
|
|
@@ -3627,8 +3714,27 @@ export class DshDeliveryRuntime {
|
|
|
3627
3714
|
// an owner tool can create/resume a goal in that very first turn and the
|
|
3628
3715
|
// host driver is otherwise free to claim its first round at idle.
|
|
3629
3716
|
goalContinuation = this.waitForNativeGoalContinuation(binding, envelope, agent, signal);
|
|
3630
|
-
acceptance = this.options.prepareForegroundTaskAcceptance(binding, envelope);
|
|
3631
|
-
foregroundExecution = this.options.beginForegroundTaskExecution(binding, envelope);
|
|
3717
|
+
acceptance = restartContinuation === undefined ? this.options.prepareForegroundTaskAcceptance(binding, envelope) : undefined;
|
|
3718
|
+
foregroundExecution = restartContinuation?.inboxId ?? this.options.beginForegroundTaskExecution(binding, envelope);
|
|
3719
|
+
if (restart !== undefined && foregroundExecution !== undefined && restart.store.getTask(foregroundExecution) !== undefined) {
|
|
3720
|
+
if (restartContinuation !== undefined) {
|
|
3721
|
+
const checkpoint = restartContinuation.checkpoint;
|
|
3722
|
+
if (checkpoint === undefined || restartDigest(agent.session.snapshotEvents().slice(0, checkpoint.sessionEventCount)) !== checkpoint.sessionDigest
|
|
3723
|
+
|| checkpoint.continuationTextDigest !== restartDigest(restartContinuationText)
|
|
3724
|
+
|| createRestartContinuation({ continuationId: restartContinuation.id, text: restartContinuationText }).id !== checkpoint.continuationMessageId) {
|
|
3725
|
+
throw new Error('assistant-delivery: restart checkpoint does not match the persisted Session');
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
restartRun = attachRestartRun({ agent, inboxId: foregroundExecution, binding, envelope,
|
|
3729
|
+
store: restart.store, config: restart.config,
|
|
3730
|
+
isRestartSafeTool: restart.isRestartSafeTool,
|
|
3731
|
+
...(restartContinuation === undefined ? {} : { continuation: restartContinuation }),
|
|
3732
|
+
authorize: () => checkAuthorization() === 'authorized' && restart.authorizeTask(foregroundExecution),
|
|
3733
|
+
authorizeRestart: () => acceptance === undefined && !goalContinuation?.pinActiveArmedGoal()
|
|
3734
|
+
&& restart.authorizeTask(foregroundExecution),
|
|
3735
|
+
});
|
|
3736
|
+
this.restartRuns.set(agent, restartRun);
|
|
3737
|
+
}
|
|
3632
3738
|
if (acceptance !== undefined || foregroundExecution !== undefined)
|
|
3633
3739
|
removeAcceptanceRequest = this.ctx.on('session/event', (session, event) => {
|
|
3634
3740
|
if (session !== agent.session || event.type !== 'request/header')
|
|
@@ -3646,11 +3752,32 @@ export class DshDeliveryRuntime {
|
|
|
3646
3752
|
// Once the durable marker exists, even a synchronous followup failure is ambiguous:
|
|
3647
3753
|
// implementations may enqueue before throwing, so no retry is safe.
|
|
3648
3754
|
dispatched = true;
|
|
3649
|
-
const message = createUserMessage({ content, source: {
|
|
3755
|
+
const message = restartContinuation === undefined ? createUserMessage({ content, source: {
|
|
3650
3756
|
kind: 'delivery', channel: envelope.channel, account: envelope.account, eventId: envelope.eventId,
|
|
3651
3757
|
trust: 'untrusted',
|
|
3652
|
-
} });
|
|
3653
|
-
let deliveryTurn = await this.followupTurn(agent, message
|
|
3758
|
+
} }) : createRestartContinuation({ continuationId: restartContinuation.id, text: restartContinuationText });
|
|
3759
|
+
let deliveryTurn = await this.followupTurn(agent, message, undefined, undefined, restartContinuation === undefined ? undefined
|
|
3760
|
+
: () => admitRestartContinuation(agent, message, { signal,
|
|
3761
|
+
authorize: () => {
|
|
3762
|
+
this.sessionLeases.assert(String(agent.session.id));
|
|
3763
|
+
if (!restart.authorize(restartContinuation))
|
|
3764
|
+
throw new Error('restart authority changed');
|
|
3765
|
+
},
|
|
3766
|
+
flush: () => sessions.flush(agent.session),
|
|
3767
|
+
onAdmitted: () => {
|
|
3768
|
+
this.sessionLeases.assert(String(agent.session.id));
|
|
3769
|
+
restart.onContinuationAdmitted(restartContinuation, String(agent.session.id));
|
|
3770
|
+
},
|
|
3771
|
+
}), signal);
|
|
3772
|
+
if (restartRun?.requested() !== undefined) {
|
|
3773
|
+
await this.waitForAgentIdle(agent, signal);
|
|
3774
|
+
restartCheckpoint = await restartRun.checkpoint();
|
|
3775
|
+
this.options.replyCommand(binding, envelope.eventId, {
|
|
3776
|
+
text: '已保存当前任务,准备进行维护重启;服务就绪后会自动继续,无需重新发送请求。', format: 'plain',
|
|
3777
|
+
}, `inbound:${foregroundExecution}:maintenance`);
|
|
3778
|
+
// Publish the maintenance handoff only after teardown and ready commit.
|
|
3779
|
+
return { outcome: 'processed' };
|
|
3780
|
+
}
|
|
3654
3781
|
let completionKind;
|
|
3655
3782
|
let automaticTurns = 0;
|
|
3656
3783
|
let assembledText = '';
|
|
@@ -3844,7 +3971,7 @@ export class DshDeliveryRuntime {
|
|
|
3844
3971
|
goalContinuationQuiescent = await goalContinuation.settle();
|
|
3845
3972
|
}
|
|
3846
3973
|
signal.throwIfAborted();
|
|
3847
|
-
|
|
3974
|
+
completionPending = true;
|
|
3848
3975
|
acceptanceSucceeded = goalContinuationQuiescent;
|
|
3849
3976
|
return { outcome: 'processed' };
|
|
3850
3977
|
}
|
|
@@ -3858,7 +3985,13 @@ export class DshDeliveryRuntime {
|
|
|
3858
3985
|
return { outcome: 'processed' };
|
|
3859
3986
|
}
|
|
3860
3987
|
if (dispatched) {
|
|
3861
|
-
|
|
3988
|
+
const timedOut = signal.reason instanceof AgentTaskTimeoutError;
|
|
3989
|
+
publishProgress({ kind: 'failed', ...(timedOut
|
|
3990
|
+
? { code: 'agent-task-timeout' } : {}) });
|
|
3991
|
+
// Preserve an exact durable diagnosis so the existing interruption
|
|
3992
|
+
// Outbox can notify the owner even when progress presentation failed.
|
|
3993
|
+
if (timedOut)
|
|
3994
|
+
return { outcome: 'not-processed', failureCode: 'agent-task-timeout', retryable: false };
|
|
3862
3995
|
throw new Error(`assistant-delivery: Agent turn became ambiguous: ${String(error)}`, { cause: error });
|
|
3863
3996
|
}
|
|
3864
3997
|
if (causedByDurableIdentity(error)) {
|
|
@@ -3906,23 +4039,49 @@ export class DshDeliveryRuntime {
|
|
|
3906
4039
|
removeProgress?.();
|
|
3907
4040
|
progressOpen = false;
|
|
3908
4041
|
let disposed = false;
|
|
4042
|
+
let maintenanceReady = false;
|
|
3909
4043
|
try {
|
|
3910
|
-
disposed = await this.disposeAfterReplyBoundary(binding.sessionId, handle, () => goalContinuation?.dispose(), !goalContinuationQuiescent,
|
|
4044
|
+
disposed = await this.disposeAfterReplyBoundary(binding.sessionId, handle, () => goalContinuation?.dispose(), !goalContinuationQuiescent, restartCheckpoint === undefined && restartContinuation === undefined
|
|
4045
|
+
? goalContinuation?.teardownTimeoutMs() ?? 10_000 : 10_000);
|
|
3911
4046
|
}
|
|
3912
4047
|
finally {
|
|
3913
4048
|
acceptanceSucceeded = acceptanceSucceeded && disposed && !signal.aborted
|
|
3914
4049
|
&& (goalContinuation?.isQuiescent() ?? true);
|
|
3915
|
-
if (foregroundExecution !== undefined) {
|
|
4050
|
+
if (foregroundExecution !== undefined && restartCheckpoint === undefined) {
|
|
3916
4051
|
try {
|
|
3917
4052
|
this.options.completeForegroundTaskExecution(foregroundExecution, {
|
|
3918
4053
|
status: acceptanceSucceeded ? 'succeeded' : 'unknown', quiescent: acceptanceSucceeded,
|
|
3919
4054
|
});
|
|
3920
4055
|
}
|
|
3921
4056
|
catch (error) {
|
|
4057
|
+
acceptanceSucceeded = false;
|
|
3922
4058
|
this.ctx.logger.warn(`assistant-delivery: foreground execution completion failed: ${String(error)}`);
|
|
3923
4059
|
}
|
|
3924
4060
|
}
|
|
3925
|
-
if (
|
|
4061
|
+
if (restart !== undefined && restartContinuation === undefined && restartCheckpoint === undefined) {
|
|
4062
|
+
const unfinished = restartRun?.requested();
|
|
4063
|
+
if (unfinished !== undefined)
|
|
4064
|
+
restart.store.fail(unfinished.id, 'restart-checkpoint-failed');
|
|
4065
|
+
}
|
|
4066
|
+
if (restart !== undefined && restartCheckpoint !== undefined) {
|
|
4067
|
+
if (disposed && !signal.aborted && restart.authorize(restartCheckpoint)) {
|
|
4068
|
+
restart.store.ready(restartCheckpoint.id);
|
|
4069
|
+
maintenanceReady = true;
|
|
4070
|
+
}
|
|
4071
|
+
else
|
|
4072
|
+
restart.store.fail(restartCheckpoint.id, 'restart-teardown-not-quiescent');
|
|
4073
|
+
}
|
|
4074
|
+
if (restart !== undefined && restartContinuation !== undefined) {
|
|
4075
|
+
const current = restart.store.getRequest(restartContinuation.id);
|
|
4076
|
+
if (current?.status === 'resuming') {
|
|
4077
|
+
if (acceptanceSucceeded)
|
|
4078
|
+
restart.store.complete(restartContinuation.id);
|
|
4079
|
+
else
|
|
4080
|
+
restart.store.fail(restartContinuation.id, 'restart-continuation-outcome-unknown');
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
restartRun?.dispose();
|
|
4084
|
+
if (acceptance !== undefined && restartCheckpoint === undefined) {
|
|
3926
4085
|
try {
|
|
3927
4086
|
await this.options.completeForegroundTaskAcceptance(acceptance, {
|
|
3928
4087
|
status: acceptanceSucceeded ? 'succeeded' : 'unknown', quiescent: acceptanceSucceeded,
|
|
@@ -3932,6 +4091,16 @@ export class DshDeliveryRuntime {
|
|
|
3932
4091
|
this.ctx.logger.warn(`assistant-delivery: acceptance completion failed: ${String(error)}`);
|
|
3933
4092
|
}
|
|
3934
4093
|
}
|
|
4094
|
+
// A provider-confirmed success cannot precede the durable execution
|
|
4095
|
+
// result: teardown can still fail after the final reply was queued.
|
|
4096
|
+
if (restartCheckpoint !== undefined)
|
|
4097
|
+
publishProgress(maintenanceReady
|
|
4098
|
+
? { kind: 'paused' } : { kind: 'failed', code: 'INTERRUPTED_RESULT_UNKNOWN' });
|
|
4099
|
+
else if (completionPending)
|
|
4100
|
+
publishProgress(acceptanceSucceeded
|
|
4101
|
+
? { kind: 'completed' } : { kind: 'failed', code: 'INTERRUPTED_RESULT_UNKNOWN' });
|
|
4102
|
+
// Terminal journal admission precedes the bounded provider drain.
|
|
4103
|
+
await drainProgress();
|
|
3935
4104
|
}
|
|
3936
4105
|
}
|
|
3937
4106
|
}
|