@yeaft/webchat-agent 1.0.295 → 1.0.298
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/index.js +1 -2
- package/local-runtime/server/db/connection.js +13 -0
- package/local-runtime/server/db/session-db.js +3 -0
- package/local-runtime/server/db/yeaft-session-db.js +9 -0
- package/local-runtime/server/handlers/agent-output.js +27 -0
- package/local-runtime/server/handlers/client-conversation.js +2 -2
- package/local-runtime/server/handlers/client-work-center.js +5 -4
- package/local-runtime/server/session-catalog.js +4 -7
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +156 -169
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/engine.js +30 -3
- package/yeaft/sessions/session-crud.js +9 -6
- package/yeaft/sessions/session-store.js +1 -0
- package/yeaft/tools/file-edit.js +8 -6
- package/yeaft/tools/file-write.js +2 -2
- package/yeaft/tools/registry.js +28 -10
- package/yeaft/tools/types.js +4 -0
- package/yeaft/web-bridge.js +10 -2
- package/yeaft/work-center/bridge.js +5 -1
- package/yeaft/work-center/controller.js +6 -2
- package/yeaft/work-center/coordinator.js +84 -17
- package/yeaft/work-center/durable-model.js +674 -0
- package/yeaft/work-center/projection.js +10 -1
- package/yeaft/work-center/runner.js +111 -16
- package/yeaft/work-center/service.js +102 -4
- package/yeaft/work-center/store.js +963 -23
|
@@ -829,7 +829,14 @@ function projectMainlineBrowser(detail) {
|
|
|
829
829
|
|
|
830
830
|
function waitingReason(detail) {
|
|
831
831
|
if (typeof detail?.waitingReason === 'string') return detail.waitingReason;
|
|
832
|
-
if (detail?.status !== 'waiting'
|
|
832
|
+
if (detail?.status !== 'waiting') return '';
|
|
833
|
+
const waitingEvent = Array.isArray(detail?.events)
|
|
834
|
+
? detail.events.find(event => event?.type === 'action.waiting'
|
|
835
|
+
&& event?.actionId === detail.currentActionId
|
|
836
|
+
&& typeof event?.data?.reason === 'string')
|
|
837
|
+
: null;
|
|
838
|
+
if (waitingEvent) return waitingEvent.data.reason;
|
|
839
|
+
if (!Array.isArray(detail.runs)) return '';
|
|
833
840
|
return detail.runs.find(run => (
|
|
834
841
|
run?.actionId === detail.currentActionId && typeof run.waitingReason === 'string'
|
|
835
842
|
))?.waitingReason || '';
|
|
@@ -1029,6 +1036,8 @@ export function projectWorkCenterEvent(event) {
|
|
|
1029
1036
|
type,
|
|
1030
1037
|
...(eventActionId ? { actionId: eventActionId } : {}),
|
|
1031
1038
|
...(typeof event?.runId === 'string' && event.runId ? { runId: event.runId } : {}),
|
|
1039
|
+
...(typeof event?.clientMessageId === 'string' && event.clientMessageId
|
|
1040
|
+
? { clientMessageId: truncateUtf8(event.clientMessageId, 256) } : {}),
|
|
1032
1041
|
workItem: {
|
|
1033
1042
|
...projectWorkItemSummary(event?.workItem),
|
|
1034
1043
|
actionStats: projectActionStats(event?.workItem, liveActionId),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Engine } from '../engine.js';
|
|
2
|
-
import { ToolRegistry } from '../tools/registry.js';
|
|
2
|
+
import { ToolRegistry, isToolErrorOutput, toolErrorEffect } from '../tools/registry.js';
|
|
3
3
|
import { defineTool } from '../tools/types.js';
|
|
4
4
|
import { allTools } from '../tools/index.js';
|
|
5
5
|
import { parsePatch } from '../tools/apply-patch.js';
|
|
@@ -265,7 +265,7 @@ export function workItemToolPolicySnapshot(workDir, attachmentRefs = [], mcpTool
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
function wrapWorkItemTool(tool, canonicalDir, canonicalAttachmentFiles, isRunActive) {
|
|
268
|
+
function wrapWorkItemTool(tool, canonicalDir, canonicalAttachmentFiles, isRunActive, operationLifecycle = null) {
|
|
269
269
|
return {
|
|
270
270
|
...tool,
|
|
271
271
|
async execute(input, ctx) {
|
|
@@ -273,12 +273,28 @@ function wrapWorkItemTool(tool, canonicalDir, canonicalAttachmentFiles, isRunAct
|
|
|
273
273
|
const checkedInput = tool.name.startsWith('mcp__')
|
|
274
274
|
? input
|
|
275
275
|
: assertToolInput(tool.name, input, canonicalDir, canonicalAttachmentFiles);
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
const trackOperation = typeof operationLifecycle === 'function'
|
|
277
|
+
&& tool.sideEffectScope !== 'run'
|
|
278
|
+
&& tool.isReadOnly?.(checkedInput) !== true;
|
|
279
|
+
const operation = trackOperation ? operationLifecycle(tool.name, checkedInput) : null;
|
|
280
|
+
let output;
|
|
281
|
+
try {
|
|
282
|
+
output = await tool.execute(checkedInput, {
|
|
283
|
+
...ctx,
|
|
284
|
+
cwd: canonicalDir,
|
|
285
|
+
workDir: canonicalDir,
|
|
286
|
+
imageAllowlist: canonicalAttachmentFiles.map(file => file.root),
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
operation?.complete('unknown', { error: String(error?.message || error) });
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
const outputHash = hashMainlineSnapshot({ output: String(output || '') });
|
|
293
|
+
const returnedError = tool.errorOutput === 'json-error-envelope' && isToolErrorOutput(output);
|
|
294
|
+
const effectStatus = returnedError
|
|
295
|
+
? toolErrorEffect(output) === 'none' ? 'failed_no_effect' : 'unknown'
|
|
296
|
+
: 'applied';
|
|
297
|
+
operation?.complete(effectStatus, { outputHash });
|
|
282
298
|
if (!isRunActive()) throw new Error('Work Center Run lease was lost during tool execution');
|
|
283
299
|
if (['FileRead', 'ViewImage'].includes(tool.name) && typeof output === 'string') {
|
|
284
300
|
const withoutFilePaths = canonicalAttachmentFiles.reduce(
|
|
@@ -376,6 +392,7 @@ export function createSubmitWorkItemPlanTool({
|
|
|
376
392
|
},
|
|
377
393
|
isConcurrencySafe: () => false,
|
|
378
394
|
isReadOnly: () => false,
|
|
395
|
+
sideEffectScope: 'run',
|
|
379
396
|
});
|
|
380
397
|
}
|
|
381
398
|
|
|
@@ -434,6 +451,7 @@ export function createProposeWorkItemActionsTool({
|
|
|
434
451
|
ctx.requestEndTurn?.({ kind: 'work_item_actions_proposed', proposalId: input.proposalId });
|
|
435
452
|
return JSON.stringify({ submitted: true, proposalId: input.proposalId, actionCount: input.actions.length });
|
|
436
453
|
},
|
|
454
|
+
sideEffectScope: 'run',
|
|
437
455
|
});
|
|
438
456
|
}
|
|
439
457
|
|
|
@@ -455,6 +473,7 @@ export function createRequestWorkItemReplanTool({ workItem, collector, isRunActi
|
|
|
455
473
|
ctx.requestEndTurn?.({ kind: 'work_item_replan_requested', proposalId: input.proposalId });
|
|
456
474
|
return JSON.stringify({ submitted: true, proposalId: input.proposalId });
|
|
457
475
|
},
|
|
476
|
+
sideEffectScope: 'run',
|
|
458
477
|
});
|
|
459
478
|
}
|
|
460
479
|
|
|
@@ -509,10 +528,13 @@ export function createSubmitWorkItemReplanTool({ vps, workItem, action, actions,
|
|
|
509
528
|
},
|
|
510
529
|
isConcurrencySafe: () => false,
|
|
511
530
|
isReadOnly: () => false,
|
|
531
|
+
sideEffectScope: 'run',
|
|
512
532
|
});
|
|
513
533
|
}
|
|
514
534
|
|
|
515
|
-
export function createWorkItemToolRegistry({
|
|
535
|
+
export function createWorkItemToolRegistry({
|
|
536
|
+
workDir, attachmentFiles = [], isRunActive, mcpTools = [], runTools = [], operationLifecycle = null,
|
|
537
|
+
}) {
|
|
516
538
|
const canonicalDir = canonicalWorkDir(path.resolve(workDir));
|
|
517
539
|
const canonicalAttachmentFiles = attachmentFiles.map(file => ({
|
|
518
540
|
...file,
|
|
@@ -522,14 +544,20 @@ export function createWorkItemToolRegistry({ workDir, attachmentFiles = [], isRu
|
|
|
522
544
|
const hasAttachments = canonicalAttachmentFiles.length > 0;
|
|
523
545
|
for (const tool of allTools) {
|
|
524
546
|
if (!WORK_ITEM_TOOL_ALLOWLIST.has(tool.name) || (hasAttachments && tool.name === 'Bash')) continue;
|
|
525
|
-
registry.register(wrapWorkItemTool(
|
|
547
|
+
registry.register(wrapWorkItemTool(
|
|
548
|
+
tool, canonicalDir, canonicalAttachmentFiles, isRunActive, operationLifecycle,
|
|
549
|
+
));
|
|
526
550
|
}
|
|
527
551
|
for (const tool of mcpTools) {
|
|
528
552
|
if (!tool?.name?.startsWith('mcp__')) continue;
|
|
529
|
-
registry.register(wrapWorkItemTool(
|
|
553
|
+
registry.register(wrapWorkItemTool(
|
|
554
|
+
tool, canonicalDir, canonicalAttachmentFiles, isRunActive, operationLifecycle,
|
|
555
|
+
));
|
|
530
556
|
}
|
|
531
557
|
for (const tool of runTools) {
|
|
532
|
-
registry.register(wrapWorkItemTool(
|
|
558
|
+
registry.register(wrapWorkItemTool(
|
|
559
|
+
tool, canonicalDir, canonicalAttachmentFiles, isRunActive, operationLifecycle,
|
|
560
|
+
));
|
|
533
561
|
}
|
|
534
562
|
return registry;
|
|
535
563
|
}
|
|
@@ -1016,12 +1044,37 @@ export class WorkItemRunner {
|
|
|
1016
1044
|
attachmentContext.files.map(file => file.ref),
|
|
1017
1045
|
[...mcpToolNames, ...runToolNames],
|
|
1018
1046
|
);
|
|
1047
|
+
let operationOrdinal = 0;
|
|
1048
|
+
const operationLifecycle = (toolName, input) => {
|
|
1049
|
+
operationOrdinal += 1;
|
|
1050
|
+
const idempotencyKey = `${run.id}:tool:${operationOrdinal}`;
|
|
1051
|
+
const claimed = this.store.createAndClaimOperation({
|
|
1052
|
+
workItemId: workItem.id,
|
|
1053
|
+
actionId: action.id,
|
|
1054
|
+
runId: run.id,
|
|
1055
|
+
operationType: toolName,
|
|
1056
|
+
idempotencyKey,
|
|
1057
|
+
replayPolicy: 'never_automatic',
|
|
1058
|
+
payload: { inputHash: hashMainlineSnapshot(input) },
|
|
1059
|
+
}, ownerBootId, run.leaseEpoch, false);
|
|
1060
|
+
if (!claimed) throw new Error(`Work Center could not claim Operation ${idempotencyKey}`);
|
|
1061
|
+
return {
|
|
1062
|
+
complete: (effectStatus, result) => {
|
|
1063
|
+
if (!this.store.completeOperation(
|
|
1064
|
+
idempotencyKey, ownerBootId, run.leaseEpoch, effectStatus, result,
|
|
1065
|
+
)) {
|
|
1066
|
+
throw new Error(`Work Center Operation ${idempotencyKey} lost its execution fence`);
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
};
|
|
1070
|
+
};
|
|
1019
1071
|
const toolRegistry = createWorkItemToolRegistry({
|
|
1020
1072
|
workDir,
|
|
1021
1073
|
attachmentFiles: attachmentContext.files,
|
|
1022
1074
|
isRunActive,
|
|
1023
1075
|
mcpTools: workspaceRuntime.mcpTools,
|
|
1024
1076
|
runTools,
|
|
1077
|
+
operationLifecycle,
|
|
1025
1078
|
});
|
|
1026
1079
|
const config = {
|
|
1027
1080
|
...runtime.config,
|
|
@@ -1136,6 +1189,7 @@ export class WorkItemRunner {
|
|
|
1136
1189
|
if (typeof registerInputWake === 'function') {
|
|
1137
1190
|
registerInputWake(() => engine.wakeForPendingUserMessage?.());
|
|
1138
1191
|
}
|
|
1192
|
+
const pendingEntriesById = new Map();
|
|
1139
1193
|
const drainPendingUserMessages = () => {
|
|
1140
1194
|
const pending = this.store.listPendingActionInputs?.(
|
|
1141
1195
|
action.id, run.id, ownerBootId, run.leaseEpoch,
|
|
@@ -1149,13 +1203,49 @@ export class WorkItemRunner {
|
|
|
1149
1203
|
const content = [item.text, attachmentLines.length > 0
|
|
1150
1204
|
? `Additional WorkItem attachments:\n${attachmentLines.join('\n')}` : '']
|
|
1151
1205
|
.filter(Boolean).join('\n\n');
|
|
1152
|
-
if (!content
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1206
|
+
if (!content) continue;
|
|
1207
|
+
pendingEntriesById.set(String(item.id), item);
|
|
1208
|
+
accepted.push({
|
|
1209
|
+
content,
|
|
1210
|
+
preview: item.text || '[attachments]',
|
|
1211
|
+
durableInputId: String(item.id),
|
|
1212
|
+
});
|
|
1156
1213
|
}
|
|
1157
1214
|
return accepted;
|
|
1158
1215
|
};
|
|
1216
|
+
const prepareProviderRequest = ({ entries, system, messages, model }) => {
|
|
1217
|
+
const durableEntries = entries
|
|
1218
|
+
.filter(entry => entry?.durableInputId)
|
|
1219
|
+
.map(entry => pendingEntriesById.get(String(entry.durableInputId)))
|
|
1220
|
+
.filter(Boolean);
|
|
1221
|
+
const requestBody = { model, system, messages };
|
|
1222
|
+
const turn = this.store.prepareEngineTurn?.(
|
|
1223
|
+
action.id, run.id, ownerBootId, run.leaseEpoch, durableEntries,
|
|
1224
|
+
{ requestBody, dispatchCapability: 'unknown' },
|
|
1225
|
+
);
|
|
1226
|
+
if (!turn) throw new Error('Work Center could not persist the next provider turn');
|
|
1227
|
+
return turn;
|
|
1228
|
+
};
|
|
1229
|
+
const startProviderRequest = turn => {
|
|
1230
|
+
if (!turn) return;
|
|
1231
|
+
const claimed = this.store.claimEngineTurn?.(turn.id, ownerBootId, run.leaseEpoch);
|
|
1232
|
+
if (!claimed) throw new Error('Work Center provider turn lost its Run lease before dispatch');
|
|
1233
|
+
};
|
|
1234
|
+
const finishProviderRequest = (turn, result) => {
|
|
1235
|
+
if (!turn) return;
|
|
1236
|
+
if (!this.store.consumeEngineTurn?.(turn.id, ownerBootId, run.leaseEpoch, result)) {
|
|
1237
|
+
throw new Error('Work Center provider response lost its EngineTurn fence');
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
const failProviderRequest = (turn, error) => {
|
|
1241
|
+
if (!turn) return;
|
|
1242
|
+
const failure = this.store.failEngineTurn?.(turn.id, ownerBootId, run.leaseEpoch, error);
|
|
1243
|
+
if (failure && failure.allowRetry === false) {
|
|
1244
|
+
error.retryable = false;
|
|
1245
|
+
error.workItemFailureKind = 'provider_dispatch_unknown';
|
|
1246
|
+
error.workItemFailureCode = 'engine_turn_dispatch_unknown';
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1159
1249
|
try {
|
|
1160
1250
|
const prompt = v2Execution
|
|
1161
1251
|
? `${renderMainlineContextSnapshot(mainline.contextSnapshot)}${fixedPromptSuffix}`
|
|
@@ -1180,9 +1270,14 @@ export class WorkItemRunner {
|
|
|
1180
1270
|
workDir,
|
|
1181
1271
|
userAlreadyPersisted: true,
|
|
1182
1272
|
drainPendingUserMessages,
|
|
1273
|
+
prepareProviderRequest,
|
|
1274
|
+
startProviderRequest,
|
|
1275
|
+
finishProviderRequest,
|
|
1276
|
+
failProviderRequest,
|
|
1183
1277
|
closePendingUserInput: () => this.store.closeRunInput(
|
|
1184
1278
|
run.id, ownerBootId, run.leaseEpoch,
|
|
1185
1279
|
),
|
|
1280
|
+
|
|
1186
1281
|
collabToolPolicy: 'single-vp',
|
|
1187
1282
|
})) {
|
|
1188
1283
|
if (event?.type === 'loop') {
|
|
@@ -60,6 +60,14 @@ function parseBoardCursor(value) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function removeUnpersistedAttachmentFiles(root, workItemId, addedAttachments, detail) {
|
|
64
|
+
if (!Array.isArray(addedAttachments) || addedAttachments.length === 0) return;
|
|
65
|
+
const persistedIds = new Set((Array.isArray(detail?.attachments) ? detail.attachments : [])
|
|
66
|
+
.map(attachment => attachment?.id).filter(Boolean));
|
|
67
|
+
const unpersisted = addedAttachments.filter(attachment => !persistedIds.has(attachment?.id));
|
|
68
|
+
if (unpersisted.length > 0) removeWorkItemAttachmentFiles(root, workItemId, unpersisted);
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
function listBoardItems(store, payload) {
|
|
64
72
|
const limit = Math.min(Math.max(Number(payload.limit) || 100, 1), 200);
|
|
65
73
|
const cursor = parseBoardCursor(payload.cursor);
|
|
@@ -106,6 +114,7 @@ export class WorkCenterService {
|
|
|
106
114
|
listAvailableVpIds: options.listAvailableVpIds,
|
|
107
115
|
});
|
|
108
116
|
this.coordinator = options.coordinator || null;
|
|
117
|
+
if (this.coordinator) this.coordinator.ownerBootId = this.ownerBootId;
|
|
109
118
|
this.onEvent = typeof options.onEvent === 'function' ? options.onEvent : () => {};
|
|
110
119
|
this.recoveryTasks = new Map();
|
|
111
120
|
this.recoveryQueue = new Map();
|
|
@@ -315,6 +324,52 @@ export class WorkCenterService {
|
|
|
315
324
|
this.#emit({ type: 'work_item.deleted', workItem: { id, revision: deleted.revision } });
|
|
316
325
|
return { id, deleted: true, cleanupWarning };
|
|
317
326
|
}
|
|
327
|
+
case 'post_work_item_message': {
|
|
328
|
+
const clientMessageId = requiredString(payload.clientMessageId, 'clientMessageId');
|
|
329
|
+
const target = payload.target && typeof payload.target === 'object' ? payload.target : {};
|
|
330
|
+
if (target.kind === 'coordinator') {
|
|
331
|
+
const receipt = this.store.getCoordinatorClientMessageReceipt(payload.id, clientMessageId);
|
|
332
|
+
if (receipt) return { accepted: true, turnId: receipt.turnId || null, duplicate: true };
|
|
333
|
+
return this.handle('work_item_message', { ...payload, clientMessageId }, requestContext);
|
|
334
|
+
}
|
|
335
|
+
if (target.kind === 'action') {
|
|
336
|
+
if (this.store.hasActionInputClientMessage(payload.id, target.actionId, clientMessageId)) {
|
|
337
|
+
return this.store.getWorkItemDetail(payload.id);
|
|
338
|
+
}
|
|
339
|
+
return this.handle('action_input', {
|
|
340
|
+
...payload,
|
|
341
|
+
clientMessageId,
|
|
342
|
+
actionId: target.actionId,
|
|
343
|
+
generation: target.generation,
|
|
344
|
+
}, requestContext);
|
|
345
|
+
}
|
|
346
|
+
if (target.kind === 'request') {
|
|
347
|
+
const id = requiredString(payload.id, 'id');
|
|
348
|
+
const workItem = this.#requiredItem(id);
|
|
349
|
+
const action = this.#requiredAction(workItem, target.actionId);
|
|
350
|
+
if (action.status === 'failed' && !String(payload.text || '').trim()
|
|
351
|
+
&& (!Array.isArray(payload.files) || payload.files.length === 0)) {
|
|
352
|
+
const detail = this.controller.retry(id, {
|
|
353
|
+
expected: {
|
|
354
|
+
actionId: action.id,
|
|
355
|
+
revision: payload.revision,
|
|
356
|
+
generation: target.generation,
|
|
357
|
+
statuses: ['failed'],
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
this.watcher.abortInvalidWorkItemRuns(id);
|
|
361
|
+
this.#emit({ type: 'action.retried', workItem: detail });
|
|
362
|
+
return detail;
|
|
363
|
+
}
|
|
364
|
+
return this.handle('action_input', {
|
|
365
|
+
...payload,
|
|
366
|
+
clientMessageId,
|
|
367
|
+
actionId: target.actionId,
|
|
368
|
+
generation: target.generation,
|
|
369
|
+
}, requestContext);
|
|
370
|
+
}
|
|
371
|
+
throw new Error('WorkItem message target is invalid');
|
|
372
|
+
}
|
|
318
373
|
case 'work_item_message': {
|
|
319
374
|
if (!this.coordinator) throw new Error('Work Center Coordinator is unavailable');
|
|
320
375
|
const id = requiredString(payload.id, 'id');
|
|
@@ -332,12 +387,18 @@ export class WorkCenterService {
|
|
|
332
387
|
planRevision: payload.planRevision,
|
|
333
388
|
ledgerRevision: payload.ledgerRevision,
|
|
334
389
|
coordinatorRevision: payload.coordinatorRevision,
|
|
390
|
+
clientMessageId: typeof payload.clientMessageId === 'string' ? payload.clientMessageId : null,
|
|
335
391
|
addedAttachments,
|
|
336
392
|
attachments: [...(workItem.attachments || []), ...addedAttachments],
|
|
337
393
|
}, {
|
|
338
394
|
onUpdate: (type, nextWorkItem) => {
|
|
339
395
|
this.watcher.abortInvalidWorkItemRuns(id);
|
|
340
|
-
this.#emit({
|
|
396
|
+
this.#emit({
|
|
397
|
+
type,
|
|
398
|
+
clientMessageId: typeof payload.clientMessageId === 'string'
|
|
399
|
+
? payload.clientMessageId : null,
|
|
400
|
+
workItem: nextWorkItem,
|
|
401
|
+
});
|
|
341
402
|
},
|
|
342
403
|
});
|
|
343
404
|
} catch (error) {
|
|
@@ -350,6 +411,7 @@ export class WorkCenterService {
|
|
|
350
411
|
} catch {}
|
|
351
412
|
throw error;
|
|
352
413
|
}
|
|
414
|
+
removeUnpersistedAttachmentFiles(this.attachmentRoot, id, addedAttachments, turn.detail);
|
|
353
415
|
turn.task.catch(() => {});
|
|
354
416
|
return { accepted: true, turnId: turn.detail.messages?.at(-1)?.turnId || null };
|
|
355
417
|
}
|
|
@@ -387,6 +449,7 @@ export class WorkCenterService {
|
|
|
387
449
|
actionId: typeof payload.actionId === 'string' ? payload.actionId : '',
|
|
388
450
|
revision: payload.revision,
|
|
389
451
|
generation,
|
|
452
|
+
clientMessageId: typeof payload.clientMessageId === 'string' ? payload.clientMessageId : null,
|
|
390
453
|
addedAttachmentCount: addedAttachments.length,
|
|
391
454
|
addedAttachments,
|
|
392
455
|
attachments: [...(workItem.attachments || []), ...addedAttachments],
|
|
@@ -401,9 +464,15 @@ export class WorkCenterService {
|
|
|
401
464
|
} catch {}
|
|
402
465
|
throw error;
|
|
403
466
|
}
|
|
467
|
+
removeUnpersistedAttachmentFiles(this.attachmentRoot, id, addedAttachments, detail);
|
|
404
468
|
this.watcher.abortInvalidWorkItemRuns(id);
|
|
405
469
|
this.watcher.notifyActionInput(id, payload.actionId);
|
|
406
|
-
this.#emit({
|
|
470
|
+
this.#emit({
|
|
471
|
+
type: 'action.input_added',
|
|
472
|
+
actionId: payload.actionId,
|
|
473
|
+
clientMessageId: typeof payload.clientMessageId === 'string' ? payload.clientMessageId : null,
|
|
474
|
+
workItem: detail,
|
|
475
|
+
});
|
|
407
476
|
return detail;
|
|
408
477
|
}
|
|
409
478
|
case 'guide': {
|
|
@@ -523,6 +592,35 @@ export class WorkCenterService {
|
|
|
523
592
|
this.#drainFailureRecoveryQueue();
|
|
524
593
|
}
|
|
525
594
|
|
|
595
|
+
#scanCoordinatorProviderRecoveries() {
|
|
596
|
+
if (this.shuttingDown || !this.coordinator) return;
|
|
597
|
+
this.store.recoverCoordinatorProviderTurns();
|
|
598
|
+
this.store.recoverCoordinatorMailbox();
|
|
599
|
+
for (const recoverable of this.store.getRecoverableCoordinatorTurns?.() || []) {
|
|
600
|
+
const claim = this.store.claimCoordinatorTurn(
|
|
601
|
+
recoverable.workItemId, recoverable.turnId, this.ownerBootId,
|
|
602
|
+
);
|
|
603
|
+
if (!claim) continue;
|
|
604
|
+
const started = this.store.resumeCoordinatorTurn(
|
|
605
|
+
recoverable.workItemId, recoverable.turnId, claim,
|
|
606
|
+
);
|
|
607
|
+
if (!started) continue;
|
|
608
|
+
const turn = this.coordinator.resume(started, {
|
|
609
|
+
text: recoverable.text || '',
|
|
610
|
+
recovery: Boolean(recoverable.recovery),
|
|
611
|
+
addedAttachments: Array.isArray(recoverable.addedAttachments)
|
|
612
|
+
? recoverable.addedAttachments : [],
|
|
613
|
+
onUpdate: (type, detail) => this.#emit({ type, workItem: detail }),
|
|
614
|
+
});
|
|
615
|
+
turn?.task?.catch?.(() => {});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#scanRecoveries() {
|
|
620
|
+
this.#scanCoordinatorProviderRecoveries();
|
|
621
|
+
this.#scanFailureRecoveries();
|
|
622
|
+
}
|
|
623
|
+
|
|
526
624
|
#scanFailureRecoveries() {
|
|
527
625
|
if (this.shuttingDown || !this.coordinator) return;
|
|
528
626
|
const now = Date.now();
|
|
@@ -584,10 +682,10 @@ export class WorkCenterService {
|
|
|
584
682
|
}
|
|
585
683
|
|
|
586
684
|
start() {
|
|
587
|
-
this.#
|
|
685
|
+
this.#scanRecoveries();
|
|
588
686
|
if (!this.recoveryTimer) {
|
|
589
687
|
this.recoveryTimer = setInterval(
|
|
590
|
-
() => this.#
|
|
688
|
+
() => this.#scanRecoveries(),
|
|
591
689
|
this.recoveryPollIntervalMs,
|
|
592
690
|
);
|
|
593
691
|
this.recoveryTimer.unref?.();
|