@yeaft/webchat-agent 1.0.365 → 1.0.367
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/local-runtime/server/handlers/agent-output.js +3 -0
- package/local-runtime/server/handlers/client-conversation.js +4 -0
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +97 -97
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/conversation/persist.js +223 -14
- package/yeaft/tools/create-work-item.js +8 -8
- package/yeaft/web-bridge.js +71 -7
- package/yeaft/work-center/controller.js +12 -2
- package/yeaft/work-center/coordinator.js +156 -26
- package/yeaft/work-center/durable-model.js +51 -1
- package/yeaft/work-center/dynamic-coordination.js +243 -0
- package/yeaft/work-center/execution-mode.js +16 -0
- package/yeaft/work-center/mainline-projection.js +25 -13
- package/yeaft/work-center/projection.js +32 -9
- package/yeaft/work-center/runner.js +18 -13
- package/yeaft/work-center/service.js +86 -13
- package/yeaft/work-center/store.js +535 -60
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isDynamicWorkItem } from './execution-mode.js';
|
|
2
3
|
import {
|
|
3
4
|
currentActionInputEventIds,
|
|
4
5
|
eventMatchesActionGeneration,
|
|
@@ -281,6 +282,7 @@ export function buildMainlineProjection(detail) {
|
|
|
281
282
|
const actions = (Array.isArray(detail.actions) ? detail.actions : []).slice().sort(stableActionOrder);
|
|
282
283
|
const runs = Array.isArray(detail.runs) ? detail.runs : [];
|
|
283
284
|
const activeActions = actions.filter(action => action.status !== 'superseded');
|
|
285
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
284
286
|
const completedStageIds = new Set(activeActions
|
|
285
287
|
.filter(action => action.status === 'completed')
|
|
286
288
|
.map(action => action.stageId));
|
|
@@ -292,10 +294,11 @@ export function buildMainlineProjection(detail) {
|
|
|
292
294
|
generation: Math.max(1, count(action.generation) || 1),
|
|
293
295
|
specHash: action.specHash || '',
|
|
294
296
|
status: action.status,
|
|
295
|
-
dependsOnStageIds: [...new Set(action.dependsOnStageIds || [])].sort(),
|
|
297
|
+
dependsOnStageIds: dynamic ? [] : [...new Set(action.dependsOnStageIds || [])].sort(),
|
|
298
|
+
sourceActionIds: dynamic ? [...new Set(action.sourceActionIds || [])].sort() : [],
|
|
296
299
|
}));
|
|
297
300
|
const frontier = nodes.filter(node => !CLOSED_ACTION_STATUSES.has(node.status)
|
|
298
|
-
&& node.dependsOnStageIds.every(stageId => completedStageIds.has(stageId)))
|
|
301
|
+
&& (dynamic || node.dependsOnStageIds.every(stageId => completedStageIds.has(stageId))))
|
|
299
302
|
.map(node => node.id);
|
|
300
303
|
const canonicalActionResults = {};
|
|
301
304
|
for (const action of activeActions) {
|
|
@@ -321,7 +324,9 @@ export function buildMainlineProjection(detail) {
|
|
|
321
324
|
goal: detail.goal || '',
|
|
322
325
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
323
326
|
},
|
|
324
|
-
|
|
327
|
+
...(dynamic
|
|
328
|
+
? { actionJournal: { revision: count(detail.planRevision), entries: nodes, runnableActionIds: frontier } }
|
|
329
|
+
: { graph: { planRevision: count(detail.planRevision), nodes, frontier } }),
|
|
325
330
|
canonicalActionResults,
|
|
326
331
|
planConflicts: (Array.isArray(detail.planConflicts) ? detail.planConflicts : [])
|
|
327
332
|
.slice()
|
|
@@ -343,14 +348,17 @@ export function buildMainlineContextSnapshot(detail, action, budgetInput = {}) {
|
|
|
343
348
|
throw mainlineContextBlocked(`Mainline fixed prompt content exceeds 64 KiB (${reservedBytes} rendered UTF-8 bytes)`);
|
|
344
349
|
}
|
|
345
350
|
const projection = buildMainlineProjection(detail);
|
|
346
|
-
const
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
351
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
352
|
+
const dependencyIds = new Set(dynamic ? action.sourceActionIds || [] : action.dependsOnStageIds || []);
|
|
353
|
+
const actionByReference = new Map((detail.actions || []).filter(candidate => candidate.status !== 'superseded')
|
|
354
|
+
.map(candidate => [dynamic ? candidate.id : candidate.stageId, candidate]));
|
|
355
|
+
const dependencies = [...dependencyIds].sort().map(reference => {
|
|
356
|
+
const dependency = actionByReference.get(reference);
|
|
357
|
+
if (!dependency) return dynamic
|
|
358
|
+
? { sourceActionId: reference, actionId: null, result: null }
|
|
359
|
+
: { stageId: reference, actionId: null, result: null };
|
|
352
360
|
return {
|
|
353
|
-
stageId,
|
|
361
|
+
...(dynamic ? { sourceActionId: reference } : { stageId: reference }),
|
|
354
362
|
actionId: dependency.id,
|
|
355
363
|
generation: dependency.generation || 1,
|
|
356
364
|
specHash: dependency.specHash || '',
|
|
@@ -377,14 +385,18 @@ export function buildMainlineContextSnapshot(detail, action, budgetInput = {}) {
|
|
|
377
385
|
policyInstruction: detail.workflowSnapshot?.actionInstructions?.[action.type]
|
|
378
386
|
|| detail.workflowSnapshot?.actionInstructions?.custom
|
|
379
387
|
|| '',
|
|
380
|
-
|
|
388
|
+
...(dynamic
|
|
389
|
+
? { sourceActionIds: [...dependencyIds].sort() }
|
|
390
|
+
: { dependsOnStageIds: [...dependencyIds].sort() }),
|
|
381
391
|
workspaceMode: action.workspaceMode || 'shared',
|
|
382
392
|
changesRequestedStageId: action.changesRequestedStageId || null,
|
|
383
393
|
},
|
|
384
394
|
},
|
|
385
|
-
|
|
395
|
+
...(dynamic
|
|
396
|
+
? { actionJournal: projection.actionJournal }
|
|
397
|
+
: { graph: projection.graph }),
|
|
386
398
|
canonicalCompletedResultsIndex: resultIndex,
|
|
387
|
-
directDependencies: dependencies,
|
|
399
|
+
...(dynamic ? { sourceResults: dependencies } : { directDependencies: dependencies }),
|
|
388
400
|
userContext: {
|
|
389
401
|
sessionContext: [],
|
|
390
402
|
workItemMessages: [],
|
|
@@ -605,6 +605,7 @@ function projectAction(action, runs, events, includeBody = true) {
|
|
|
605
605
|
? (action.assignmentPolicy || null)
|
|
606
606
|
: projectAssignmentPolicy(action.assignmentPolicy),
|
|
607
607
|
dependsOnStageIds: Array.isArray(action.dependsOnStageIds) ? action.dependsOnStageIds : [],
|
|
608
|
+
sourceActionIds: Array.isArray(action.sourceActionIds) ? action.sourceActionIds : [],
|
|
608
609
|
workspaceMode: action.workspaceMode || 'shared',
|
|
609
610
|
requiredRole: action.requiredRole || '',
|
|
610
611
|
generation: Math.max(1, count(action.generation) || 1),
|
|
@@ -797,17 +798,20 @@ function projectCanonicalEvidence(value) {
|
|
|
797
798
|
}
|
|
798
799
|
|
|
799
800
|
function projectMainlineBrowser(detail) {
|
|
800
|
-
if (!detail?.id || detail.executionSchemaVersion
|
|
801
|
+
if (!detail?.id || Number(detail.executionSchemaVersion) < 2) return null;
|
|
801
802
|
const mainline = buildMainlineProjection(detail);
|
|
803
|
+
const actionSet = mainline.actionJournal || mainline.graph;
|
|
804
|
+
const nodes = actionSet.entries || actionSet.nodes || [];
|
|
805
|
+
const frontier = actionSet.runnableActionIds || actionSet.frontier || [];
|
|
802
806
|
const actionById = new Map((detail.actions || []).map(action => [action.id, action]));
|
|
803
807
|
const activeActionIds = Array.isArray(detail.activeActionIds)
|
|
804
808
|
? detail.activeActionIds
|
|
805
|
-
:
|
|
809
|
+
: nodes.filter(node => ['ready', 'running'].includes(node.status)).map(node => node.id);
|
|
806
810
|
const attentionActionIds = Array.isArray(detail.attentionActionIds)
|
|
807
811
|
? detail.attentionActionIds
|
|
808
|
-
:
|
|
812
|
+
: nodes.filter(node => ['waiting', 'failed'].includes(node.status)).map(node => node.id);
|
|
809
813
|
const counts = Object.fromEntries(['completed', 'running', 'ready', 'waiting', 'failed']
|
|
810
|
-
.map(status => [status,
|
|
814
|
+
.map(status => [status, nodes.filter(node => node.status === status).length]));
|
|
811
815
|
return {
|
|
812
816
|
contract: {
|
|
813
817
|
title: truncateUtf8(mainline.contract.title, 8_000),
|
|
@@ -816,15 +820,15 @@ function projectMainlineBrowser(detail) {
|
|
|
816
820
|
.map(criterion => truncateUtf8(criterion, 4_000)),
|
|
817
821
|
},
|
|
818
822
|
progress: {
|
|
819
|
-
lifecycle: detail.lifecycle || (counts.completed ===
|
|
823
|
+
lifecycle: detail.lifecycle || (counts.completed === nodes.length ? 'done' : 'active'),
|
|
820
824
|
attentionState: detail.attentionState || (counts.waiting && counts.failed ? 'mixed'
|
|
821
825
|
: counts.waiting ? 'waiting' : counts.failed ? 'failed' : 'none'),
|
|
822
826
|
activeActionIds: [...activeActionIds],
|
|
823
827
|
attentionActionIds: [...attentionActionIds],
|
|
824
|
-
frontierActionIds: [...
|
|
828
|
+
frontierActionIds: [...frontier],
|
|
825
829
|
counts,
|
|
826
830
|
},
|
|
827
|
-
actions:
|
|
831
|
+
actions: nodes.map(node => {
|
|
828
832
|
const action = actionById.get(node.id) || {};
|
|
829
833
|
const result = mainline.canonicalActionResults[node.id];
|
|
830
834
|
return {
|
|
@@ -839,7 +843,7 @@ function projectMainlineBrowser(detail) {
|
|
|
839
843
|
truncateUtf8(value, MAX_CURRENT_BRIEF_BYTES),
|
|
840
844
|
]))
|
|
841
845
|
: null,
|
|
842
|
-
dependencies: [...node.dependsOnStageIds],
|
|
846
|
+
dependencies: [...(node.sourceActionIds?.length ? node.sourceActionIds : node.dependsOnStageIds || [])],
|
|
843
847
|
canonicalResult: result ? {
|
|
844
848
|
status: result.status,
|
|
845
849
|
summary: sanitizeMainlineDiagnostic(result.summary, MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
@@ -895,6 +899,22 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
895
899
|
planRevision: count(detail.planRevision),
|
|
896
900
|
ledgerRevision: count(detail.ledgerRevision),
|
|
897
901
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
902
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
903
|
+
finalResult: detail.finalResult && typeof detail.finalResult === 'object' ? {
|
|
904
|
+
summary: truncateUtf8(detail.finalResult.summary || '', MAX_ACTION_MESSAGE_CHARS),
|
|
905
|
+
acceptanceResults: Array.isArray(detail.finalResult.acceptanceResults)
|
|
906
|
+
? detail.finalResult.acceptanceResults.slice(0, 24).map(result => ({
|
|
907
|
+
criterion: truncateUtf8(result?.criterion || '', MAX_ACTION_MESSAGE_CHARS),
|
|
908
|
+
status: result?.status === 'passed' ? 'passed' : null,
|
|
909
|
+
evidenceRunIds: Array.isArray(result?.evidenceRunIds)
|
|
910
|
+
? result.evidenceRunIds.map(String).slice(0, 24) : [],
|
|
911
|
+
})) : [],
|
|
912
|
+
evidenceRunIds: Array.isArray(detail.finalResult.evidenceRunIds)
|
|
913
|
+
? detail.finalResult.evidenceRunIds.map(String).slice(0, 64) : [],
|
|
914
|
+
residualRisks: Array.isArray(detail.finalResult.residualRisks)
|
|
915
|
+
? detail.finalResult.residualRisks
|
|
916
|
+
.map(risk => truncateUtf8(risk, MAX_ACTION_MESSAGE_CHARS)).slice(0, 24) : [],
|
|
917
|
+
} : null,
|
|
898
918
|
title: detail.title,
|
|
899
919
|
goal: detail.goal,
|
|
900
920
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
@@ -931,7 +951,8 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
931
951
|
status: ['thinking', 'completed', 'failed'].includes(message.status) ? message.status : 'completed',
|
|
932
952
|
error: truncateUtf8(message.error || '', MAX_ACTION_DIAGNOSTIC_CHARS) || null,
|
|
933
953
|
decision: message.decision && typeof message.decision === 'object' ? {
|
|
934
|
-
kind: ['answer', 'guide_actions', 'replan', 'request_human']
|
|
954
|
+
kind: ['answer', 'create_actions', 'guide_actions', 'replan', 'request_human', 'complete']
|
|
955
|
+
.includes(message.decision.kind)
|
|
935
956
|
? message.decision.kind : null,
|
|
936
957
|
reason: truncateUtf8(message.decision.reason || '', MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
937
958
|
changedContract: message.decision.changedContract === true,
|
|
@@ -981,6 +1002,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
981
1002
|
planRevision: count(detail.planRevision),
|
|
982
1003
|
ledgerRevision: count(detail.ledgerRevision),
|
|
983
1004
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
1005
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
984
1006
|
title: detail.title,
|
|
985
1007
|
goal: detail.goal,
|
|
986
1008
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -1017,6 +1039,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
1017
1039
|
planRevision: count(detail.planRevision),
|
|
1018
1040
|
ledgerRevision: count(detail.ledgerRevision),
|
|
1019
1041
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
1042
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
1020
1043
|
title: detail.title,
|
|
1021
1044
|
goal: detail.goal,
|
|
1022
1045
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -34,6 +34,7 @@ import { MCPManager } from '../mcp.js';
|
|
|
34
34
|
import { buildMcpFlattenedTools } from '../tools/mcp-tools.js';
|
|
35
35
|
import { recallWorkspaceSessionContext } from './workspace-context.js';
|
|
36
36
|
import { applyGeneratedPlan, BUILT_IN_ACTION_TYPES } from './workflow.js';
|
|
37
|
+
import { isDynamicWorkItem, usesMainlineContext } from './execution-mode.js';
|
|
37
38
|
import {
|
|
38
39
|
applyAdditivePlanProposal,
|
|
39
40
|
applyReplanMutation,
|
|
@@ -946,7 +947,9 @@ export class WorkItemRunner {
|
|
|
946
947
|
action: finalizeOwnedIntegration(this.store, action, run, ownerBootId),
|
|
947
948
|
};
|
|
948
949
|
}
|
|
949
|
-
const dependencies =
|
|
950
|
+
const dependencies = isDynamicWorkItem(workItem)
|
|
951
|
+
? this.store.listActionSources(workItem.id, action.sourceActionIds || [])
|
|
952
|
+
: this.store.listActionDependencies(workItem.id, action.dependsOnStageIds || []);
|
|
950
953
|
if (dependencies.length > 0 && dependencies.every(dependency => (
|
|
951
954
|
dependency.workspaceMode === 'shared' && !dependency.workspace?.isolated
|
|
952
955
|
))) {
|
|
@@ -1021,9 +1024,8 @@ export class WorkItemRunner {
|
|
|
1021
1024
|
|
|
1022
1025
|
async run({ workItem, action, run, signal, ownerBootId, onProgress, registerProgressReader, registerInputWake }) {
|
|
1023
1026
|
const runtime = await this.runtimeProvider();
|
|
1024
|
-
const currentSettings = workItem?.workflowSnapshot?.planningMode
|
|
1025
|
-
? await this.policyProvider()
|
|
1026
|
-
: null;
|
|
1027
|
+
const currentSettings = ['ai', 'coordinator'].includes(workItem?.workflowSnapshot?.planningMode)
|
|
1028
|
+
&& this.policyProvider ? await this.policyProvider() : null;
|
|
1027
1029
|
const currentModelPolicy = currentSettings?.actionModelPolicies?.[action.type]
|
|
1028
1030
|
|| currentSettings?.actionModelPolicies?.custom
|
|
1029
1031
|
|| currentSettings?.modelPolicy
|
|
@@ -1036,15 +1038,16 @@ export class WorkItemRunner {
|
|
|
1036
1038
|
? resolveWorkItemWorkDir({ workspaceKey: action.workspace.path }, runtime.defaultWorkDir)
|
|
1037
1039
|
: workspaceDir;
|
|
1038
1040
|
const priorRuns = this.store.listCompletedRuns(workItem.id);
|
|
1039
|
-
const
|
|
1040
|
-
const dependencyContext =
|
|
1041
|
-
? []
|
|
1042
|
-
:
|
|
1041
|
+
const mainlineExecution = usesMainlineContext(workItem);
|
|
1042
|
+
const dependencyContext = isDynamicWorkItem(workItem)
|
|
1043
|
+
? this.store.listActionSources?.(workItem.id, action.sourceActionIds || []) || []
|
|
1044
|
+
: mainlineExecution ? []
|
|
1045
|
+
: this.store.listActionDependencies?.(workItem.id, action.dependsOnStageIds || []) || [];
|
|
1043
1046
|
const dependencyBlock = dependencyContext.length === 0 ? '' : `\n\nCompleted dependency results:\n${dependencyContext.map(dependency => {
|
|
1044
1047
|
const evidence = dependency.evidence?.length
|
|
1045
1048
|
? `\nEvidence: ${dependency.evidence.map(item => item.label).join('; ')}`
|
|
1046
1049
|
: '';
|
|
1047
|
-
return `### ${dependency.stageId} (${dependency.vpId || 'unknown VP'})\n${dependency.summary || '(no summary)'}${evidence}`;
|
|
1050
|
+
return `### ${isDynamicWorkItem(workItem) ? dependency.id : dependency.stageId} (${dependency.vpId || 'unknown VP'})\n${dependency.summary || '(no summary)'}${evidence}`;
|
|
1048
1051
|
}).join('\n\n')}`;
|
|
1049
1052
|
const resumeBlock = renderActionResumeBlock(this.store.getActionResumeContext?.(action.id, run.id));
|
|
1050
1053
|
const assignment = executionAction.assignmentPolicy
|
|
@@ -1084,7 +1087,7 @@ export class WorkItemRunner {
|
|
|
1084
1087
|
const attachmentFileById = new Map(attachmentContext.files.map(file => [file.id, file]));
|
|
1085
1088
|
const fixedPromptSuffix = `${resumeBlock}${attachmentContext.promptBlock}${completionContract(executionAction, workItem)}`;
|
|
1086
1089
|
const reservedPromptBytes = Buffer.byteLength(fixedPromptSuffix, 'utf8');
|
|
1087
|
-
const mainline =
|
|
1090
|
+
const mainline = mainlineExecution
|
|
1088
1091
|
? buildMainlineContextSnapshot(
|
|
1089
1092
|
this.store.getWorkItemDetail(workItem.id),
|
|
1090
1093
|
executionAction,
|
|
@@ -1212,7 +1215,9 @@ export class WorkItemRunner {
|
|
|
1212
1215
|
executionManifest: mainline ? {
|
|
1213
1216
|
schemaVersion: 2,
|
|
1214
1217
|
ledgerRevision: mainline.contextSnapshot.ledgerRevision,
|
|
1215
|
-
planRevision:
|
|
1218
|
+
planRevision: isDynamicWorkItem(workItem)
|
|
1219
|
+
? mainline.contextSnapshot.actionJournal.revision
|
|
1220
|
+
: mainline.contextSnapshot.graph.planRevision,
|
|
1216
1221
|
contractRevision: mainline.contextSnapshot.contract.revision,
|
|
1217
1222
|
actionGeneration: mainline.contextSnapshot.action.generation,
|
|
1218
1223
|
actionSpecHash: mainline.contextSnapshot.action.specHash,
|
|
@@ -1336,11 +1341,11 @@ export class WorkItemRunner {
|
|
|
1336
1341
|
}
|
|
1337
1342
|
};
|
|
1338
1343
|
try {
|
|
1339
|
-
const prompt =
|
|
1344
|
+
const prompt = mainlineExecution
|
|
1340
1345
|
? `${renderMainlineContextSnapshot(mainline.contextSnapshot)}${fixedPromptSuffix}`
|
|
1341
1346
|
: `${executionAction.instruction}${dependencyBlock}${resumeBlock}${attachmentContext.promptBlock}${workspaceSessionBlock}${memoryBlock}${completionContract(executionAction, workItem)}`;
|
|
1342
1347
|
const promptBytes = Buffer.byteLength(prompt, 'utf8');
|
|
1343
|
-
if (
|
|
1348
|
+
if (mainlineExecution && promptBytes > MAINLINE_CONTEXT_HARD_LIMIT_BYTES) {
|
|
1344
1349
|
throw new Error(`Work Center Mainline prompt exceeds 64 KiB (${promptBytes} rendered UTF-8 bytes)`);
|
|
1345
1350
|
}
|
|
1346
1351
|
const promptParts = attachmentContext.promptParts.length > 0
|
|
@@ -23,9 +23,12 @@ import { readWorkCenterSettings, writeWorkCenterSettings } from './settings.js';
|
|
|
23
23
|
import {
|
|
24
24
|
defaultWorkCenterStageInstructions,
|
|
25
25
|
listWorkItemTypeTemplates,
|
|
26
|
-
resolvePlanningWorkflowSnapshot,
|
|
27
|
-
resolveWorkflowSnapshot,
|
|
28
26
|
} from './workflow.js';
|
|
27
|
+
import {
|
|
28
|
+
DYNAMIC_COORDINATION_MODE,
|
|
29
|
+
DYNAMIC_EXECUTION_SCHEMA_VERSION,
|
|
30
|
+
resolveDynamicActionPolicySnapshot,
|
|
31
|
+
} from './dynamic-coordination.js';
|
|
29
32
|
|
|
30
33
|
function requiredString(value, name) {
|
|
31
34
|
if (typeof value !== 'string' || !value.trim()) throw new Error(`${name} is required`);
|
|
@@ -122,6 +125,7 @@ export class WorkCenterService {
|
|
|
122
125
|
? Number(options.pollIntervalMs)
|
|
123
126
|
: 2_000;
|
|
124
127
|
this.recoveryTimer = null;
|
|
128
|
+
this.dynamicCoordinatorTasks = new Map();
|
|
125
129
|
this.shuttingDown = false;
|
|
126
130
|
this.watcher = new WorkItemWatcher({
|
|
127
131
|
store: this.store,
|
|
@@ -227,14 +231,8 @@ export class WorkCenterService {
|
|
|
227
231
|
}
|
|
228
232
|
case 'create': {
|
|
229
233
|
const settings = this.settingsReader(this.yeaftDir);
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
? payload.workflowTemplate.trim()
|
|
233
|
-
: null;
|
|
234
|
-
const workflowTemplate = explicitWorkflow || 'ai-planned';
|
|
235
|
-
const workflowSnapshot = explicitWorkflow
|
|
236
|
-
? resolveWorkflowSnapshot(settings, explicitWorkflow, payload.stageOverrides)
|
|
237
|
-
: resolvePlanningWorkflowSnapshot(settings, payload.workItemType);
|
|
234
|
+
const workflowTemplate = 'coordinator-driven';
|
|
235
|
+
const workflowSnapshot = resolveDynamicActionPolicySnapshot(settings, payload.workItemType);
|
|
238
236
|
const runtime = !Object.hasOwn(payload, 'workDir') && !settings.defaultWorkDir
|
|
239
237
|
? await this.runtimeInfo()
|
|
240
238
|
: null;
|
|
@@ -248,6 +246,7 @@ export class WorkCenterService {
|
|
|
248
246
|
root: this.attachmentRoot,
|
|
249
247
|
workItemId,
|
|
250
248
|
});
|
|
249
|
+
const shouldStart = payload.start === undefined ? settings.startImmediately : payload.start !== false;
|
|
251
250
|
this.controller.create({
|
|
252
251
|
id: workItemId,
|
|
253
252
|
title: requiredString(payload.title, 'title'),
|
|
@@ -257,6 +256,8 @@ export class WorkCenterService {
|
|
|
257
256
|
: [],
|
|
258
257
|
workflowTemplate,
|
|
259
258
|
workflowSnapshot,
|
|
259
|
+
coordinationMode: DYNAMIC_COORDINATION_MODE,
|
|
260
|
+
executionSchemaVersion: DYNAMIC_EXECUTION_SCHEMA_VERSION,
|
|
260
261
|
workDir,
|
|
261
262
|
reuseMemory: payload.reuseMemory !== false,
|
|
262
263
|
origin: payload.origin && typeof payload.origin === 'object'
|
|
@@ -274,9 +275,13 @@ export class WorkCenterService {
|
|
|
274
275
|
? normalizeSessionContextSnapshot(payload.sessionContext)
|
|
275
276
|
: [],
|
|
276
277
|
attachments,
|
|
277
|
-
start:
|
|
278
|
+
start: false,
|
|
278
279
|
});
|
|
279
|
-
|
|
280
|
+
let detail = this.#requiredItem(workItemId);
|
|
281
|
+
if (shouldStart) {
|
|
282
|
+
detail = this.controller.start(workItemId);
|
|
283
|
+
this.#queueDynamicCoordinatorWake(workItemId);
|
|
284
|
+
}
|
|
280
285
|
this.#emit({ type: 'work_item.created', workItem: detail });
|
|
281
286
|
return detail;
|
|
282
287
|
} catch (error) {
|
|
@@ -288,11 +293,17 @@ export class WorkCenterService {
|
|
|
288
293
|
const id = requiredString(payload.id, 'id');
|
|
289
294
|
const detail = this.controller.update(id, payload.patch || {});
|
|
290
295
|
this.watcher.abortInvalidWorkItemRuns(id);
|
|
296
|
+
if (detail.coordinationMode === DYNAMIC_COORDINATION_MODE) {
|
|
297
|
+
this.#queueDynamicCoordinatorWake(id);
|
|
298
|
+
}
|
|
291
299
|
this.#emit({ type: 'work_item.updated', workItem: detail });
|
|
292
300
|
return detail;
|
|
293
301
|
}
|
|
294
302
|
case 'start': {
|
|
295
303
|
const detail = this.controller.start(requiredString(payload.id, 'id'));
|
|
304
|
+
if (detail.coordinationMode === DYNAMIC_COORDINATION_MODE) {
|
|
305
|
+
this.#queueDynamicCoordinatorWake(detail.id);
|
|
306
|
+
}
|
|
296
307
|
this.#emit({ type: 'work_item.started', workItem: detail });
|
|
297
308
|
return detail;
|
|
298
309
|
}
|
|
@@ -307,6 +318,9 @@ export class WorkCenterService {
|
|
|
307
318
|
const id = requiredString(payload.id, 'id');
|
|
308
319
|
const detail = this.controller.resume(id, { revision: payload.revision });
|
|
309
320
|
this.watcher.abortInvalidWorkItemRuns(id);
|
|
321
|
+
if (detail.coordinationMode === DYNAMIC_COORDINATION_MODE) {
|
|
322
|
+
this.#queueDynamicCoordinatorWake(id);
|
|
323
|
+
}
|
|
310
324
|
this.#emit({ type: 'work_item.resumed', workItem: detail });
|
|
311
325
|
return detail;
|
|
312
326
|
}
|
|
@@ -571,6 +585,10 @@ export class WorkCenterService {
|
|
|
571
585
|
|
|
572
586
|
#emit(event) {
|
|
573
587
|
try { this.onEvent(event); } catch {}
|
|
588
|
+
if (event?.workItem?.coordinationMode === DYNAMIC_COORDINATION_MODE) {
|
|
589
|
+
this.#queueDynamicCoordinatorWake(event.workItem.id);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
574
592
|
if (['run.finished', 'coordinator.turn_completed'].includes(event?.type)) {
|
|
575
593
|
for (const action of event.workItem?.actions || []) {
|
|
576
594
|
if (action.status !== 'failed') continue;
|
|
@@ -583,6 +601,56 @@ export class WorkCenterService {
|
|
|
583
601
|
}
|
|
584
602
|
}
|
|
585
603
|
|
|
604
|
+
#queueDynamicCoordinatorWake(workItemId) {
|
|
605
|
+
if (this.shuttingDown || !this.coordinator || !workItemId
|
|
606
|
+
|| this.dynamicCoordinatorTasks.has(workItemId)) return;
|
|
607
|
+
this.dynamicCoordinatorTasks.set(workItemId, null);
|
|
608
|
+
queueMicrotask(() => {
|
|
609
|
+
if (this.dynamicCoordinatorTasks.get(workItemId) === null) {
|
|
610
|
+
this.dynamicCoordinatorTasks.delete(workItemId);
|
|
611
|
+
this.#drainDynamicCoordinatorWakes(workItemId);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
#scanDynamicCoordinatorWakes() {
|
|
617
|
+
if (this.shuttingDown || !this.coordinator) return;
|
|
618
|
+
for (const entry of this.store.listPendingDynamicCoordinatorWakes()) {
|
|
619
|
+
this.#queueDynamicCoordinatorWake(entry.workItemId);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
#drainDynamicCoordinatorWakes(workItemId) {
|
|
624
|
+
if (this.shuttingDown || !this.coordinator || this.dynamicCoordinatorTasks.has(workItemId)) return null;
|
|
625
|
+
const entries = this.store.listPendingDynamicCoordinatorWakes()
|
|
626
|
+
.filter(candidate => candidate.workItemId === workItemId);
|
|
627
|
+
const entry = entries.find(candidate => candidate.payload?.turnId) || entries[0];
|
|
628
|
+
if (!entry) return null;
|
|
629
|
+
const detail = this.store.getWorkItemDetail(workItemId);
|
|
630
|
+
if (detail?.actions?.some(action => action.status === 'running')) return null;
|
|
631
|
+
let turn;
|
|
632
|
+
try {
|
|
633
|
+
turn = this.coordinator.advance(entry.id, {
|
|
634
|
+
workItemId,
|
|
635
|
+
onUpdate: (type, workItem) => this.#emit({ type, workItem }),
|
|
636
|
+
});
|
|
637
|
+
} catch (error) {
|
|
638
|
+
this.#emit({
|
|
639
|
+
type: 'coordinator.advance_schedule_failed',
|
|
640
|
+
workItem: this.store.getWorkItemDetail(workItemId),
|
|
641
|
+
error: error?.message || String(error),
|
|
642
|
+
});
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
if (!turn) return null;
|
|
646
|
+
const task = turn.task.finally(() => {
|
|
647
|
+
this.dynamicCoordinatorTasks.delete(workItemId);
|
|
648
|
+
});
|
|
649
|
+
this.dynamicCoordinatorTasks.set(workItemId, task);
|
|
650
|
+
task.catch(() => {});
|
|
651
|
+
return task;
|
|
652
|
+
}
|
|
653
|
+
|
|
586
654
|
#recoveryKey(entry) {
|
|
587
655
|
return `${entry.workItemId}:${entry.actionId}:${entry.actionGeneration}`;
|
|
588
656
|
}
|
|
@@ -621,6 +689,7 @@ export class WorkCenterService {
|
|
|
621
689
|
|
|
622
690
|
#scanRecoveries() {
|
|
623
691
|
this.#scanCoordinatorProviderRecoveries();
|
|
692
|
+
this.#scanDynamicCoordinatorWakes();
|
|
624
693
|
this.#scanFailureRecoveries();
|
|
625
694
|
}
|
|
626
695
|
|
|
@@ -701,8 +770,12 @@ export class WorkCenterService {
|
|
|
701
770
|
if (this.recoveryTimer) clearInterval(this.recoveryTimer);
|
|
702
771
|
this.recoveryTimer = null;
|
|
703
772
|
await this.coordinator?.shutdown?.();
|
|
704
|
-
await Promise.allSettled([
|
|
773
|
+
await Promise.allSettled([
|
|
774
|
+
...this.recoveryTasks.values(),
|
|
775
|
+
...[...this.dynamicCoordinatorTasks.values()].filter(Boolean),
|
|
776
|
+
]);
|
|
705
777
|
this.recoveryTasks.clear();
|
|
778
|
+
this.dynamicCoordinatorTasks.clear();
|
|
706
779
|
this.recoveryQueue.clear();
|
|
707
780
|
await this.watcher.stop();
|
|
708
781
|
try { await this.watcher.runner?.shutdown?.(); } catch {}
|